From f8d1ad26892f5edb031b4396dfc261ba6883dfd0 Mon Sep 17 00:00:00 2001 From: "Simon L." Date: Mon, 31 Aug 2026 12:27:48 +0200 Subject: [PATCH 1/3] rewrite pi-hole and pivpn scripts to use docker containers Signed-off-by: Simon L. --- lib.sh | 2 +- nextcloud_update.sh | 4 + not-supported/firewall.sh | 27 +- not-supported/not-supported_menu.sh | 12 +- not-supported/pi-hole.sh | 715 +++++++++++++++------------- not-supported/restore-backup.sh | 2 +- not-supported/wireguard.sh | 448 +++++++++++++++++ {not-supported => old}/pivpn.sh | 0 8 files changed, 852 insertions(+), 358 deletions(-) create mode 100644 not-supported/wireguard.sh rename {not-supported => old}/pivpn.sh (100%) diff --git a/lib.sh b/lib.sh index 00d8567d92..8450390a4d 100644 --- a/lib.sh +++ b/lib.sh @@ -54,7 +54,7 @@ INTERNET_DNS="9.9.9.9" # Default Quad9 DNS servers, overwritten by the systemd global DNS defined servers, if set DNS1="9.9.9.9" DNS2="149.112.112.112" -NONO_PORTS=(22 25 53 80 443 1024 3012 3306 5178 5179 5432 7867 7983 8983 10000 8081 8443 9443 9000 9980 9090 9200 9600 1234) +NONO_PORTS=(22 25 53 80 443 1024 3012 3306 5178 5179 5432 7867 7983 8094 8573 8983 10000 8081 8443 9443 9000 9980 9090 9200 9600 1234 51820 51821 51822) # 9000 9980 9090 9200 9600 1234 are local docker ports, don't remember if they are needed here or not. use_global_systemd_dns() { if [ -f "/etc/systemd/resolved.conf" ] diff --git a/nextcloud_update.sh b/nextcloud_update.sh index 1b05bf3770..7fc9455074 100644 --- a/nextcloud_update.sh +++ b/nextcloud_update.sh @@ -753,6 +753,10 @@ $DOCKER_RUN_OUTPUT" docker_update_specific 'jellyfin' "Jellyfin Media Server" # Imaginary docker_update_specific 'imaginary' "Imaginary" + # Pi-hole + docker_update_specific 'pihole' "Pi-hole" + # WireGuard: the image is pinned to a major tag, hence this only updates within it + docker_update_specific 'wg-easy' "WireGuard" fi # Fix Collabora change too coolwsd diff --git a/not-supported/firewall.sh b/not-supported/firewall.sh index a6f476a492..ddd1a4dfcd 100644 --- a/not-supported/firewall.sh +++ b/not-supported/firewall.sh @@ -86,20 +86,31 @@ then ufw allow samba comment Samba fi -# Pi-hole -if pihole &>/dev/null +# Pi-hole. Port 53 is published by the container, which docker opens in the +# nat table before ufw, hence only the proxy port needs a rule. +if is_docker_running && docker ps -a --format "{{.Names}}" | grep -q "^pihole$" then print_text_in_color "$ICyan" "Allow Pi-hole" - ufw allow 53/tcp comment 'Pi-hole TCP' - ufw allow 53/udp comment 'Pi-hole UDP' ufw allow 8094/tcp comment 'Pi-hole Web' + # unbound runs on the host and is reached via the docker bridge, + # which does hit the INPUT chain and hence needs a rule + if [ -f /etc/unbound/unbound.conf.d/pi-hole.conf ] + then + DOCKER_GATEWAY="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null)" + if [ -z "$DOCKER_GATEWAY" ] + then + DOCKER_GATEWAY=172.17.0.1 + fi + ufw allow in on docker0 to "$DOCKER_GATEWAY" port 5335 comment 'Pi-hole unbound' + fi fi -# PiVPN -if pivpn &>/dev/null +# WireGuard +# The VPN port is published by the container, see the Pi-hole comment above +if is_docker_running && docker ps -a --format "{{.Names}}" | grep -q "^wg-easy$" then - print_text_in_color "$ICyan" "Allow PiVPN" - ufw allow 51820/udp comment 'PiVPN' + print_text_in_color "$ICyan" "Allow WireGuard" + ufw allow 51822/tcp comment 'WireGuard Web' fi # Plex diff --git a/not-supported/not-supported_menu.sh b/not-supported/not-supported_menu.sh index 96778f4fa7..196bf60761 100644 --- a/not-supported/not-supported_menu.sh +++ b/not-supported/not-supported_menu.sh @@ -39,11 +39,11 @@ $CHECKLIST_GUIDE" "$WT_HEIGHT" "$WT_WIDTH" 4 \ "Monitor Link Shares" "(Monitors the creation of link shares)" OFF \ "Off-Shore Backup Wizard" "(Create an Off-Shore Backup script)" OFF \ "Pi-hole" "(Network wide ads- and tracker blocking)" OFF \ -"PiVPN" "(Install a Wireguard VPN server with PiVPN)" OFF \ "PLEX Media Server" "(Multimedia server application)" OFF \ "Remotedesktop" "(Install a remotedesktop based on xrdp)" OFF \ "SMB-server" "(Create and manage a SMB-server on OS level)" OFF \ -"System Restore" "(Restore the system partition from a backup)" OFF 3>&1 1>&2 2>&3) +"System Restore" "(Restore the system partition from a backup)" OFF \ +"WireGuard" "(Install a WireGuard VPN server)" OFF 3>&1 1>&2 2>&3) case "$choice" in *"BTRFS Format"*) @@ -102,10 +102,6 @@ case "$choice" in print_text_in_color "$ICyan" "Downloading the Pi-hole script..." run_script NOT_SUPPORTED_FOLDER pi-hole ;;& - *"PiVPN"*) - print_text_in_color "$ICyan" "Downloading the PiVPN script..." - run_script NOT_SUPPORTED_FOLDER pivpn - ;;& *"PLEX Media Server"*) print_text_in_color "$ICyan" "Downloading the PLEX Media Server script..." run_script NOT_SUPPORTED_FOLDER plexmediaserver @@ -122,6 +118,10 @@ case "$choice" in print_text_in_color "$ICyan" "Downloading the System Restore script..." run_script NOT_SUPPORTED_FOLDER system-restore ;;& + *"WireGuard"*) + print_text_in_color "$ICyan" "Downloading the WireGuard script..." + run_script NOT_SUPPORTED_FOLDER wireguard + ;;& *) ;; esac diff --git a/not-supported/pi-hole.sh b/not-supported/pi-hole.sh index 443c4c324f..f6afdd0e0a 100644 --- a/not-supported/pi-hole.sh +++ b/not-supported/pi-hole.sh @@ -3,12 +3,13 @@ # T&M Hansson IT AB © - 2026, https://www.hanssonit.se/ # Copyright © 2021 Simon Lindner (https://github.com/szaimen) -# shellcheck disable=2016,2034,2059,2178,2317 true SCRIPT_NAME="Pi-hole" SCRIPT_EXPLAINER="The Pi-hole® is a DNS sinkhole that protects your devices from unwanted content, \ without installing any client-side software. -This is their official website: https://pi-hole.net" +This is their official website: https://pi-hole.net + +This script installs Pi-hole in a Docker container." # shellcheck source=lib.sh source /var/scripts/fetch_lib.sh @@ -21,304 +22,282 @@ debug_mode # Check if root root_check -msg_box "The pi-hole script is unfortunately deprecated as it needs a rewrite since many parts in the upstream pi-hole project changed. -Feel free to subscribe to https://github.com/szaimen/Nextcloud-NAS-Guide/issues/133 in the meantime." -exit 1 +# The port that the Pi-hole web interface listens on inside the container. +# We don't use 80 here since that port is already occupied by Apache2 on the host. +PIHOLE_WEB_PORT=8573 +# The port that Apache2 listens on to proxy the web interface via https +PIHOLE_PROXY_PORT=8094 +# Where the Pi-hole configuration and databases are stored on the host +PIHOLE_DIR=/opt/pihole # Check if already installed -if ! pihole &>/dev/null +if ! is_docker_running || ! docker ps -a --format "{{.Names}}" | grep -q "^pihole$" then # Ask for installing install_popup "$SCRIPT_NAME" else - # Choose to uninstall - if ! yesno_box_no "It seems like Pi-hole is already installed. -Do you want to uninstall Pi-hole and reset all its settings?" - then - exit 1 - fi + # Ask for removal or reinstallation + reinstall_remove_menu "$SCRIPT_NAME" - # Check if PiVPN is installed - if pivpn &>/dev/null + # The user-data is kept, so an upstream pointing at the unbound that we + # remove below would survive and break DNS. Reset it while the CLI still works. + if [ -f /etc/unbound/unbound.conf.d/pi-hole.conf ] \ + && docker ps --format "{{.Names}}" | grep -q "^pihole$" then - msg_box "It seems like PiVPN is installed. -We recommend urgently to uninstall PiVPN before uninstalling Pi-hole \ -because it could happen, that PiVPN doesn't work anymore after uninstalling Pi-hole." - exit 1 + print_text_in_color "$ICyan" "Resetting the Pi-hole upstream DNS servers..." + docker exec pihole pihole-FTL --config dns.upstreams \ +'[ "9.9.9.9", "149.112.112.112" ]' &>/dev/null fi - - # Warning - msg_box "Warning! -Uninstalling Pi-hole will reset all its config and will reboot your NcVM afterwards automatically." - - # Last choice - if ! yesno_box_no "Do you want to continue nonetheless?" + # Removal + docker rm -f pihole &>/dev/null + # Remove the Apache2 configuration + if [ -f "$SITES_AVAILABLE/pihole.conf" ] then - exit 1 + a2dissite pihole.conf &>/dev/null + rm -f "$SITES_AVAILABLE/pihole.conf" + restart_webserver fi - - # Get initially installed programs from pihole-update.sh - INSTALLED=$(grep "Pi-hole installed programs=" "$SCRIPTS/pihole-update.sh") - INSTALLED="${INSTALLED##*programs=}" - - # Inform the user - if ! yesno_box_yes "These are all packets that where installed during your initial Pi-hole installation: -$INSTALLED - -Do they look correct to you? If not, you can press 'no' and we will not remove anything. -If you press 'yes', we will remove Pi-hole, its settings and all those listed programs." + # Delete firewall entries + ufw delete allow "$PIHOLE_PROXY_PORT/tcp" &>/dev/null + # Delete the leftover rules of former installations that added them for port 53 + for port in 53/tcp 53/udp + do + ufw delete allow "$port" &>/dev/null + done + # Delete the unbound rule, if it exists + DOCKER_GATEWAY="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null)" + if [ -z "$DOCKER_GATEWAY" ] then - exit 1 + DOCKER_GATEWAY=172.17.0.1 fi - - # Make an array from installed applications - read -r -a INSTALLED <<< "$INSTALLED" - - # /opt/pihole/uninstall.sh edit file and put setupVars variable setupVars="/etc/pihole/setupVars.conf" at 5th line or something - - UNINSTALL="/etc/.pihole/automated install/uninstall.sh" - # Uninstall pihole; we need to modify it, else it is not unattended - if ! [ -f "$UNINSTALL" ] || ! grep -q "######### SCRIPT ###########" "$UNINSTALL" || ! grep -q "removeNoPurge()" "$UNINSTALL" + ufw delete allow in on docker0 to "$DOCKER_GATEWAY" port 5335 comment 'Pi-hole unbound' &>/dev/null + # Remove unbound, since it was only installed for Pi-hole + if [ -f /etc/unbound/unbound.conf.d/pi-hole.conf ] then - msg_box "It seems like some uninstall functions changed. -Please report this to $ISSUES" - exit 1 + rm -f /etc/unbound/unbound.conf.d/pi-hole.conf + rm -f /etc/systemd/system/unbound.service.d/ncvm-pihole.conf + rmdir /etc/systemd/system/unbound.service.d &>/dev/null + systemctl daemon-reload + if is_this_installed unbound + then + apt-get purge unbound -y + apt-get autoremove -y + fi fi - - # Continue with preparation - check_command cp "/etc/.pihole/automated install/uninstall.sh" "$SCRIPTS"/pihole-uninstall.sh - check_command sed -i '/######### SCRIPT ###########/q' "$SCRIPTS"/pihole-uninstall.sh - check_command echo "removeNoPurge" >> "$SCRIPTS"/pihole-uninstall.sh - - # Uninstall Pi-hole - check_command yes | bash "$SCRIPTS"/pihole-uninstall.sh - - # Remove the file and crontab - crontab -u root -l | grep -v "pihole-update.sh" | crontab -u root - - check_command rm "$SCRIPTS"/pihole-uninstall.sh - - # Delete the pihole user - if id pihole &>/dev/null + # Re-enable the systemd-resolved stub listener since port 53 is free again + if [ -f /etc/systemd/resolved.conf.d/ncvm-pihole.conf ] then - check_command killall -u pihole - check_command deluser pihole &>/dev/null - check_command groupdel pihole + rm -f /etc/systemd/resolved.conf.d/ncvm-pihole.conf + systemctl restart systemd-resolved &>/dev/null + # Restore the resolv.conf symlink to the stub resolver + if [ -f /run/systemd/resolve/stub-resolv.conf ] + then + ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf + elif ! [ -s /etc/resolv.conf ] + then + # Neither the stub nor a usable file exists, so write a static one + printf 'nameserver 9.9.9.9\nnameserver 149.112.112.112\n' > /etc/resolv.conf + fi fi - - # Delete all its config data - rm -rf /etc/.pihole - rm -rf /etc/pihole - rm -rf /opt/pihole - rm -rf /usr/bin/pihole-FTL - rm -rf /usr/local/bin/pihole - rm -rf /var/www/html/admin - rm -f /var/www/html/pihole - - # Delete unbound config - crontab -u root -l | grep -v "systemctl restart unbound" | crontab -u root - - rm /etc/unbound/unbound.conf.d/pi-hole.conf - - # Remove update script - rm -f "$SCRIPTS/pihole-update.sh" - - # Remove all initially installed applications - for program in "${INSTALLED[@]}" - do - apt-get purge "$program" -y - done - - # Remove unbound - if is_this_installed unbound + # The user-data is kept on purpose so that a reinstallation doesn't lose the settings + if [ "$REINSTALL_REMOVE" = "Uninstall" ] then - apt-get purge unbound -y + msg_box "The Pi-hole user-data was NOT removed and is still stored here: +'$PIHOLE_DIR' + +If you want to delete it as well, e.g. to be able to start from scratch \ +if you install Pi-hole again later on, please run the following command: +'sudo rm -r $PIHOLE_DIR' + +Attention! Please don't forget to reset the DNS server on your router and/or \ +your clients to restore their internet connectivity, if you had configured them \ +to use this server as their DNS server." + else + msg_box "Please note that the Pi-hole user-data in '$PIHOLE_DIR' \ +will be kept, which means that your current settings, blocklists and \ +statistics will still be there after the reinstallation. + +If you want to start from scratch instead, please abort this script now with 'CTRL+C' \ +and run the following command before running it again: +'sudo rm -r $PIHOLE_DIR'" fi - - # Remove not needed dependencies - apt-get autoremove -y - - # Delete other files - rm -f /var/www/html/index.lighttpd.orig - rm -rf /etc/lighttpd - - # Remove apache conf - a2dissite pihole.conf &>/dev/null - rm -f "$SITES_AVAILABLE/pihole.conf" - restart_webserver - - # Delete firewall entry - ufw delete allow 53/tcp &>/dev/null - ufw delete allow 53/udp &>/dev/null - ufw delete allow 8094/tcp &>/dev/null - - # Inform the user - msg_box "Pi-hole was successfully uninstalled! -Please reset the DNS on your router/clients to restore internet connectivity" - msg_box "After you hit OK, your NcVM will get restarted." - rm -f "$SCRIPTS/pi-hole.sh" - # Reboot the NcVM because it would cause problems if not - reboot + # Show successful uninstall if applicable + removal_popup "$SCRIPT_NAME" fi -# Inform the user -msg_box "Before installing the Pi-hole, please make sure that you have a backup of your NcVM. -The reason is, that to install the Pi-hole we will need to run a 3rd party script on your NcVM. -Something could go wrong. So please keep backups!" +# Warn about running this on a public server +msg_box "Please note that Pi-hole is only intended to be run on a server \ +in a trusted home network. -# Ask if backups are ready -if ! yesno_box_no "Have you made a backup of your NcVM? -This is the last possibility to quit! -If you choose 'yes' we will continue with the installtion." +You should NOT run this on a public VPS or any other server whose ip address \ +is directly reachable from the internet, since an open DNS resolver can be \ +abused for DNS amplification attacks." + +if ! yesno_box_yes "Is this server running in a trusted home network?" then exit 1 fi -# Inform the user -print_text_in_color "$ICyan" "Installing Pi-hole..." - -# Download the script -mkdir -p "$SCRIPTS" -check_command curl -sfL https://install.pi-hole.net -o "$SCRIPTS"/pihole-install.sh - -# Check that all patterns match -if ! grep -q 'displayFinalMessage "${pw}"' "$SCRIPTS"/pihole-install.sh || ! grep -q "setAdminFlag$" "$SCRIPTS"/pihole-install.sh \ -|| ! grep -q "chooseInterface$" "$SCRIPTS"/pihole-install.sh || ! grep -q "getStaticIPv4Settings$" "$SCRIPTS"/pihole-install.sh +# Check for a leftover Pi-hole installation from former versions of this script. +# It runs Pi-hole on the host and occupies port 53 and the lighttpd web interface. +if [ -d /etc/.pihole ] || [ -f /usr/local/bin/pihole ] || [ -f /usr/bin/pihole-FTL ] then - msg_box "It seems like some functions in pihole-install.sh have changed. -Please report this to $ISSUES" - exit 1 -fi + msg_box "It seems like an old Pi-hole installation is still present on this server. -# Continue with the process -sed -i 's|displayFinalMessage "${pw}"|echo displayFinalMessage|' "$SCRIPTS"/pihole-install.sh # We don't want to display the final message -sed -i "s|setAdminFlag$|echo setAdminFlag|" "$SCRIPTS"/pihole-install.sh # We want to install the web-interface and lighttpd -sed -i "s|chooseInterface$|echo chooseInterface|" "$SCRIPTS"/pihole-install.sh # We don't want the user choose the interface -sed -i "s|getStaticIPv4Settings$|echo getStaticIPv4Settings|" "$SCRIPTS"/pihole-install.sh # We don't want to set a static ip4 +Former versions of this script installed Pi-hole directly on the host. It occupies \ +port 53 and runs its web interface via lighttpd, which means that the new Pi-hole \ +container would not be able to start. -# Export default values -PIHOLE_INTERFACE="$IFACE" -export PIHOLE_INTERFACE +You need to uninstall the old Pi-hole first. You can do this by running the \ +following command: +'sudo pihole uninstall' -# Fix php versions getting hold for pi-hole install script -apt-mark unhold php"$PHPVER"* +Afterwards, please remove the leftovers with the following commands: +'sudo rm -rf /etc/.pihole /etc/pihole /etc/lighttpd' +'sudo rm -f /usr/local/bin/pihole /usr/bin/pihole-FTL' -# Run the script -bash "$SCRIPTS"/pihole-install.sh | tee "$SCRIPTS"/pihole-install.report +Please note that this will remove all your current Pi-hole settings and \ +blocklists. You will have to configure them again afterwards. -# Get all installed and remove pihole-install.sh -unset INSTALLED -INSTALLED=$(grep "Checking for" "$SCRIPTS"/pihole-install.report | grep "will be installed" | awk '{print $8}') -check_command rm "$SCRIPTS"/pihole-install.sh -check_command rm "$SCRIPTS"/pihole-install.report +Please uninstall the old Pi-hole and run this script again." + exit 1 +fi -# Check if at least one app got installed -if [ -z "${INSTALLED[*]}" ] +# Pi-hole needs port 53, which the systemd-resolved stub listener occupies by +# default. We disable it below, but other DNS servers are up to the user. +print_text_in_color "$ICyan" "Checking if port 53 is already in use..." +# Our own container is already removed here, so any leftover is a real conflict. +# We exclude systemd-resolved by its PID, since 'ss' truncates the process name. +RESOLVED_PID="$(systemctl show -p MainPID --value systemd-resolved 2>/dev/null)" +DNS_IN_USE="$(ss -tulpn 2>/dev/null | grep ":53 ")" +if [ -n "$RESOLVED_PID" ] && [ "$RESOLVED_PID" != "0" ] then - msg_bos "Something is wrong. Didn't expect that no requirement get installed. -Please report this to $ISSUES" + DNS_IN_USE="$(echo "$DNS_IN_USE" | grep -v "pid=$RESOLVED_PID,")" fi +if [ -n "$DNS_IN_USE" ] +then + msg_box "It seems like another DNS server is already listening on port 53: -# Make an array from installed applications -mapfile -t INSTALLED <<< "${INSTALLED[@]}" +$DNS_IN_USE -# Create update script -mkdir -p "$SCRIPTS" +Pi-hole cannot be installed while another DNS server occupies this port. \ +Please stop and disable that DNS server first and run this script again. -# Insert the new lines into pihole-update.sh -cat << PIHOLE_UPDATE > "$SCRIPTS/pihole-update.sh" -#!/bin/bash -if [ -f /var/scripts/fetch_lib.sh ] -then - source /var/scripts/fetch_lib.sh -elif ! source <(curl -sL https://raw.githubusercontent.com/nextcloud/vm/main/static/fetch_lib.sh) -then - source <(curl -sL https://cdn.statically.io/gh/nextcloud/vm/main/static/fetch_lib.sh) -fi -PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin -notify_admin_gui "Starting the Pi-hole update." "You will be notified when it is done." -# Create backup first -if [ -f "\$SCRIPTS/daily-borg-backup.sh" ] -then - rm -f /tmp/DAILY_BACKUP_CREATION_SUCCESSFUL - export SKIP_DAILY_BACKUP_CHECK=1 - bash "\$SCRIPTS/daily-borg-backup.sh" - if ! [ -f "/tmp/DAILY_BACKUP_CREATION_SUCCESSFUL" ] - then - notify_admin_gui "Pi-hole update failed because backup could not be created!" \ - "Could not create a backup! \$(date +%T)" - exit 1 - fi -fi -check_command pihole -up -systemctl stop lighttpd -check_command sed -i 's|^server\.port.*|server\.port = 8093|' /etc/lighttpd/lighttpd.conf -sleep 10 # Wait for lighttpd -check_command systemctl start lighttpd -# Please don't remove or change this line! Pi-hole installed programs=${INSTALLED[@]} -notify_admin_gui "Pi-hole update successful!" "" -PIHOLE_UPDATE - -# Secure the file -chown root:root "$SCRIPTS/pihole-update.sh" -chmod 700 "$SCRIPTS/pihole-update.sh" - -# Check if Pi-hole was successfully installed -if ! pihole &>/dev/null -then - msg_box "Something got wrong during pihole-install.sh -Please report this to $ISSUES" +Please report this to $ISSUES if you think that this is a mistake." exit 1 fi -# Set up REV_SERVER for local DNS entries because Pi-hole isn't the DHCP server and some other settings -if [ -f /etc/pihole/setupVars.conf ] && ! grep -q "REV_SERVER" /etc/pihole/setupVars.conf +# Ask if the user wants to use unbound as recursive DNS server +if yesno_box_yes "Do you want to enable your Pi-hole to be a recursive DNS server? + +If you choose 'yes', we will additionally install unbound and configure your \ +Pi-hole to use it as its upstream DNS server. This means that your Pi-hole will \ +resolve all DNS queries itself instead of forwarding them to a public DNS \ +provider like Google or Cloudflare, which improves your privacy." then - cat << PIHOLE_CONF >> /etc/pihole/setupVars.conf -REV_SERVER=true -REV_SERVER_CIDR=$(ip route | grep -v "default via" | grep "$IFACE" | awk '{print $1}' | grep "/") -REV_SERVER_TARGET=$GATEWAY -REV_SERVER_DOMAIN= -PIHOLE_CONF + UNBOUND=yes fi -# Make sure that local DNS entries work -if [ -f /etc/pihole/setupVars.conf ] && ! grep -q "DNS_FQDN_REQUIRED" /etc/pihole/setupVars.conf && ! grep -q "DNS_BOGUS_PRIV" /etc/pihole/setupVars.conf +# Install Docker +install_docker + +# Free port 53 by disabling the systemd-resolved stub listener. systemd-resolved +# keeps running as the host resolver but stops listening on 127.0.0.53:53. +print_text_in_color "$ICyan" "Disabling the systemd-resolved DNS stub listener..." +mkdir -p /etc/systemd/resolved.conf.d +cat << RESOLVED_CONF > /etc/systemd/resolved.conf.d/ncvm-pihole.conf +# This file was created by the NcVM Pi-hole script. Pi-hole needs to bind to +# port 53, which is not possible while the stub listener occupies 127.0.0.53:53. +[Resolve] +DNSStubListener=no +RESOLVED_CONF + +# With the stub listener disabled, /etc/resolv.conf must not point to the +# stub resolver anymore, since nothing is listening on 127.0.0.53 any longer. +if [ -f /run/systemd/resolve/resolv.conf ] then - cat << PIHOLE_CONF >> /etc/pihole/setupVars.conf -DNS_FQDN_REQUIRED=false -DNS_BOGUS_PRIV=false -PIHOLE_CONF + ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf +elif [ -L /etc/resolv.conf ] && readlink -f /etc/resolv.conf | grep -q "stub-resolv.conf" +then + # systemd-resolved doesn't provide the uplink file, so we would be left + # without any working resolver. Write a static one instead. + rm -f /etc/resolv.conf + printf 'nameserver 9.9.9.9\nnameserver 149.112.112.112\n' > /etc/resolv.conf fi -# Wait for pihole to restart -print_text_in_color "$ICyan" "Restarting pihole..." -sleep 5 +check_command systemctl restart systemd-resolved -# Try to restart Pi-hole to apply the new settings -if ! pihole restartdns +# Make sure that name resolution still works before we continue, +# since we just changed the DNS setup of the host +print_text_in_color "$ICyan" "Checking if DNS resolution still works..." +install_if_not dnsutils +if ! nslookup github.com >/dev/null 2>&1 then - msg_box "Something got wrong during the Pi-hole restart. + msg_box "DNS resolution stopped working after disabling the systemd-resolved \ +stub listener. We will revert this change now and exit. + Please report this to $ISSUES" + rm -f /etc/systemd/resolved.conf.d/ncvm-pihole.conf + # Restart first, so that systemd-resolved recreates the stub file that we + # link to below. Otherwise the host is left without a working resolver. + systemctl restart systemd-resolved + if [ -f /run/systemd/resolve/stub-resolv.conf ] + then + ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf + fi exit 1 fi -# Change the port to 8093 -check_command sudo sed -i '/^server.port/s/80/8093/' /etc/lighttpd/lighttpd.conf +# Create the directories for the persistent data +mkdir -p "$PIHOLE_DIR/etc-pihole" +mkdir -p "$PIHOLE_DIR/etc-dnsmasq.d" -# Wait for lighttpd to startup -print_text_in_color "$ICyan" "Restarting lighttpd..." -sleep 5 +# Generate a new Pi-hole password +PASSWORD=$(gen_passwd 12 "a-zA-Z0-9") -# Restart lighttpd -if ! systemctl restart lighttpd +# Get the docker container +print_text_in_color "$ICyan" "Getting Pi-hole..." +if ! docker pull pihole/pihole:latest then - msg_box "Couldn't restart lighttpd. -Please report this to $ISSUES" + msg_box "Failed to download the Pi-hole container image. + +Please check your internet connection and report this issue here $ISSUES \ +if you can't solve it yourself." exit 1 fi -# Install Apache2 -print_text_in_color "$ICyan" "Configuring Apache..." +# Create Pi-hole. DHCP is not enabled on purpose, hence no NET_ADMIN capability. +# 'dns_listeningMode=all' is needed for queries from outside the bridge network. +print_text_in_color "$ICyan" "Installing Pi-hole..." +if ! docker run -d \ +--name pihole \ +--restart always \ +-p 53:53/tcp \ +-p 53:53/udp \ +-p 127.0.0.1:"$PIHOLE_WEB_PORT":"$PIHOLE_WEB_PORT"/tcp \ +-e TZ="$(cat /etc/timezone)" \ +-e FTLCONF_webserver_api_password="$PASSWORD" \ +-e FTLCONF_dns_listeningMode=all \ +-e FTLCONF_webserver_port="$PIHOLE_WEB_PORT" \ +-v "$PIHOLE_DIR/etc-pihole:/etc/pihole" \ +-v "$PIHOLE_DIR/etc-dnsmasq.d:/etc/dnsmasq.d" \ +pihole/pihole:latest +then + msg_box "Failed to create the Pi-hole container. + +Please report this issue here $ISSUES if you can't solve it yourself." + # Remove the container leftovers so that this script can be run again + docker rm -f pihole &>/dev/null + exit 1 +fi + +# Add prune command +add_dockerprune + +# Install apache2 install_if_not apache2 + +# Enable Apache2 module's a2enmod headers a2enmod rewrite a2enmod ssl @@ -331,40 +310,43 @@ then TLS13="+TLSv1.3" fi +# Create the vhost that proxies the Pi-hole web interface via https. The cert is +# self-signed since the admin interface is only reachable in the local network. cat << PIHOLE_CONF > "$SITES_AVAILABLE/pihole.conf" -Listen 8094 - +Listen $PIHOLE_PROXY_PORT + Header add Strict-Transport-Security: "max-age=15768000;includeSubdomains" - + # Intermediate configuration SSLEngine on SSLCompression off SSLProtocol -all +TLSv1.2 $TLS13 - SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder off SSLSessionTickets off ServerSignature off - + # Logs LogLevel warn CustomLog \${APACHE_LOG_DIR}/access.log combined ErrorLog \${APACHE_LOG_DIR}/error.log - + # Just in case - see below SSLProxyEngine On SSLProxyVerify None SSLProxyCheckPeerCN Off SSLProxyCheckPeerName Off - - # This is needed to redirect access on http://$ADDRESS:8094/ to https://$ADDRESS:8094/ - ErrorDocument 400 https://$ADDRESS:8094/admin/ - + + # This is needed to redirect access on http://$ADDRESS:$PIHOLE_PROXY_PORT/ + # to https://$ADDRESS:$PIHOLE_PROXY_PORT/ + ErrorDocument 400 https://$ADDRESS:$PIHOLE_PROXY_PORT/admin/ + # basic proxy settings ProxyRequests off - ProxyPass / "http://127.0.0.1:8093/" - ProxyPassReverse / "http://127.0.0.1:8093/" + ProxyPass / "http://127.0.0.1:$PIHOLE_WEB_PORT/" + ProxyPassReverse / "http://127.0.0.1:$PIHOLE_WEB_PORT/" ProxyPreserveHost On - + ### LOCATION OF CERT FILES ### SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key @@ -382,69 +364,49 @@ The script will exit." exit 1 fi -# Generate new Pi-hole password -PASSWORD=$(gen_passwd 12 "a-zA-Z0-9") - -# Set a new admin password -check_command pihole -a -p "$PASSWORD" - -# Get the ipv6-address from the config file -IPV6_ADDRESS=$(grep "IPV6_ADDRESS=" /etc/pihole/setupVars.conf) -IPV6_ADDRESS="${IPV6_ADDRESS##*IPV6_ADDRESS=}" - -# Create contab entry -crontab -u root -l | grep -v "pihole-update.sh" | crontab -u root - -crontab -u root -l | { cat; echo "30 19 * * 6 $SCRIPTS/pihole-update.sh >/dev/null" ; } | crontab -u root - - -# Add firewall entry -ufw allow 53/tcp comment 'Pi-hole TCP' &>/dev/null -ufw allow 53/udp comment 'Pi-hole UDP' &>/dev/null -ufw allow 8094/tcp comment 'Pi-hole Web' &>/dev/null - -# Show that everything was set up correctly -msg_box "Congratulations, your Pi-hole was set up correctly! -It is now reachable on: -https://$ADDRESS:8094/admin - -Your password is: $PASSWORD" - -# Show the address -msg_box "You can now configure your devices to use the Pi-hole as their DNS server using: -IPv4: $ADDRESS -IPv6: ${IPV6_ADDRESS:-Not Configured}" - -# Show how to use pihole in the command line -msg_box "How to use Pi-hole on the command line: - -You can reset the Pi-hole admin password by running: -'pihole -a -p' - -A list of available options is shown by running: -'pihole -h'" - -# Inform about updates -msg_box "Concerning updates: -We have created an update script that you can use to update your Pi-hole by running: -'bash $SCRIPTS/pihole-update.sh' - -Updates will automatically be executed every saturday at 19:30" +# Add firewall rules. Port 53 is published by the container, which docker opens +# in the nat table before ufw. Former host installations opened it directly. +for port in 53/tcp 53/udp +do + ufw delete allow "$port" &>/dev/null +done +ufw allow "$PIHOLE_PROXY_PORT"/tcp comment 'Pi-hole Web' &>/dev/null -# Ask if the user wants to install unbound -if ! yesno_box_yes "Do you want to enables your Pi-hole to be a recursive DNS server? -If you press 'yes', we will install unbound and configure your Pi-hole to use that." +# Set up unbound if chosen +if [ "$UNBOUND" = "yes" ] then - exit -fi + # Install unbound. We do not use install_if_not here, since it installs + # with RUNLEVEL=1, which skips the postinst that creates the DNSSEC anchor. + if ! is_this_installed unbound + then + apt-get update -q4 & spinner_loading + check_command apt-get install unbound -y + fi -# Install needed tools -install_if_not unbound + # unbound listens on the docker bridge gateway so that the container can + # reach it, since 127.0.0.1 would not be reachable from inside it. + DOCKER_GATEWAY="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}')" + DOCKER_SUBNET="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}')" + if [ -z "$DOCKER_GATEWAY" ] + then + DOCKER_GATEWAY=172.17.0.1 + fi + # The subnet is not necessarily in 172.16.0.0/12, since it can be changed + # via 'default-address-pools' in the docker daemon configuration + if [ -z "$DOCKER_SUBNET" ] + then + DOCKER_SUBNET=172.17.0.0/16 + fi -cat << UNBOUND_CONF > /etc/unbound/unbound.conf.d/pi-hole.conf + cat << UNBOUND_CONF > /etc/unbound/unbound.conf.d/pi-hole.conf server: # To see what those variables do, look here: # https://docs.pi-hole.net/guides/unbound/ verbosity: 0 - interface: 127.0.0.1 + interface: $DOCKER_GATEWAY + # docker0 doesn't exist yet when unbound starts after a reboot, + # which is why we need to allow binding to a not yet existing address + ip-freebind: yes port: 5335 do-ip4: yes do-udp: yes @@ -454,7 +416,7 @@ server: harden-glue: yes harden-dnssec-stripped: yes use-caps-for-id: no - edns-buffer-size: 1472 + edns-buffer-size: 1232 prefetch: yes num-threads: 1 so-rcvbuf: 1m @@ -464,48 +426,117 @@ server: private-address: 10.0.0.0/8 private-address: fd00::/8 private-address: fe80::/10 + # Only allow the Pi-hole container to use this resolver + access-control: 0.0.0.0/0 refuse + access-control: 127.0.0.0/8 allow + access-control: $DOCKER_SUBNET allow UNBOUND_CONF -# Wait for unbound to restart -print_text_in_color "$ICyan" "Restarting unbound..." -sleep 10 & spinner_loading - -# Restart unbound -check_command service unbound restart - -# Testing DNSSEC -if ! dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335 | grep -q "SERVFAIL" -then - msg_box "Something got wrong while testing SERVFAIL. + # Make sure that unbound starts after docker, so that the bridge that it + # listens on exists and is reachable after a reboot + mkdir -p /etc/systemd/system/unbound.service.d + cat << UNBOUND_SERVICE > /etc/systemd/system/unbound.service.d/ncvm-pihole.conf +# This file was created by the NcVM Pi-hole script +[Unit] +After=docker.service +UNBOUND_SERVICE + systemctl daemon-reload + + # Restart unbound + print_text_in_color "$ICyan" "Restarting unbound..." + check_command systemctl restart unbound + countdown "Waiting for unbound to start... " 10 + + # Testing DNSSEC + install_if_not dnsutils + if ! dig sigfail.verteiltesysteme.net @"$DOCKER_GATEWAY" -p 5335 | grep -q "SERVFAIL" + then + msg_box "Something went wrong while testing SERVFAIL. Please report this to $ISSUES" -elif ! dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335 | grep -q "NOERROR" -then - msg_box "Something got wrong while testing NOERROR. + elif ! dig sigok.verteiltesysteme.net @"$DOCKER_GATEWAY" -p 5335 | grep -q "NOERROR" + then + msg_box "Something went wrong while testing NOERROR. Please report this to $ISSUES" -fi + fi -# Set up Pi-hole -sed -i 's|^PIHOLE_DNS_1=.*|PIHOLE_DNS_1=127.0.0.1#5335|' /etc/pihole/setupVars.conf -sed -i '/^PIHOLE_DNS_2=.*/d' /etc/pihole/setupVars.conf + # Allow the container to reach unbound on the docker bridge + ufw allow in on docker0 to "$DOCKER_GATEWAY" port 5335 comment 'Pi-hole unbound' &>/dev/null -# Wait for pihole to restart -print_text_in_color "$ICyan" "Restarting pihole..." -sleep 5 + # Configure Pi-hole to use unbound as its upstream DNS server + print_text_in_color "$ICyan" "Configuring Pi-hole to use unbound..." + # Wait for pihole-FTL to accept config changes instead of guessing a delay, + # since writing the config too early is silently lost on startup + PIHOLE_READY=no + for _ in $(seq 1 30) + do + if docker exec pihole pihole-FTL --config dns.upstreams &>/dev/null + then + PIHOLE_READY=yes + break + fi + sleep 2 + done + # 'dns.upstreams' is an array, hence the value needs to be a json array. + # The key and the value need to be separate arguments to actually set it. + if [ "$PIHOLE_READY" != "yes" ] || ! docker exec pihole pihole-FTL --config dns.upstreams "[ \"$DOCKER_GATEWAY#5335\" ]" &>/dev/null + then + msg_box "Could not configure Pi-hole to use unbound automatically. -# Try to restart Pi-hole to apply the new settings -if ! pihole restartdns -then - msg_box "Something got wrong during the Pi-hole unbound restart. -Please report this to $ISSUES" - exit 1 +You can do this yourself by visiting https://$ADDRESS:$PIHOLE_PROXY_PORT/admin \ +and entering '$DOCKER_GATEWAY#5335' as custom upstream DNS server under \ +'Settings' --> 'DNS'." + elif ! docker restart pihole &>/dev/null + then + msg_box "Pi-hole was configured to use unbound, but the container could \ +not be restarted to apply it. + +Please restart it yourself with 'sudo docker restart pihole' and report this \ +issue here $ISSUES if it keeps failing." + else + msg_box "unbound was successfully installed and Pi-hole was successfully \ +configured to use it as recursive DNS server." + fi fi -# Fix dns disconnections -crontab -u root -l | grep -v "systemctl restart unbound" | crontab -u root - -crontab -u root -l | { cat; echo "@hourly systemctl restart unbound" ; } | crontab -u root - +# Show that everything was set up correctly +msg_box "Congratulations, your Pi-hole was set up correctly! +It is now reachable on: +https://$ADDRESS:$PIHOLE_PROXY_PORT/admin + +Your password is: $PASSWORD + +Please note that the certificate is self-signed, which means that your browser \ +will show a warning that you need to accept." + +# Show the address +msg_box "You can now configure your devices to use the Pi-hole as their DNS server \ +by entering the following ip address as DNS server in your router: +$ADDRESS + +Additionally, you can configure the docker daemon to use it by editing \ +'/etc/docker/daemon.json' and adding '\"dns\" : [ \"$ADDRESS\", \"9.9.9.9\" ]'." + +# Show how to use pihole in the command line +msg_box "How to use Pi-hole on the command line: + +You can run any Pi-hole command inside the container like this: +'sudo docker exec -it pihole pihole -h' + +Please note that the admin password is set via an environment variable of the \ +container, which makes it read-only for the web interface and the command line. \ +If you want to change it, you can run this script again and choose 'Reinstall', \ +which will generate and show you a new password while keeping all your settings. + +Please also note that the DHCP functionality of Pi-hole is not enabled since the \ +container doesn't run in the host network." + +# Inform about updates +msg_box "Concerning updates: +Pi-hole runs in a Docker container, which means that you can update it \ +by running the following commands: +'sudo docker pull pihole/pihole:latest' +and afterwards running this script again and choosing 'Reinstall'. -# Inform the user -msg_box "Congratulations! -Unbound was successfully installed and Pi-hole was successfully configured as recursive DNS server." +Your settings and statistics in '$PIHOLE_DIR' will be kept in that process." exit diff --git a/not-supported/restore-backup.sh b/not-supported/restore-backup.sh index 633aa5084b..05c8fe9dd4 100644 --- a/not-supported/restore-backup.sh +++ b/not-supported/restore-backup.sh @@ -718,7 +718,7 @@ msg_box "Restore completed!\n You can now simply reinstall all apps and addons that were installed on your server before!\n Those need to get installed (if they were installed on the old server before): Geoblocking, Disk Monitoring, Fail2Ban, ClamAV, SMTP Mail, DDclient, Activate TLS, EuroOffice, Push Notifications for Nextcloud, \ -High-Performance backend for Nextcloud Talk, Whiteboard for Nextcloud, Vaultwarden, Pi-hole, PiVPN, \ +High-Performance backend for Nextcloud Talk, Whiteboard for Nextcloud, Vaultwarden, Pi-hole, WireGuard, \ Plex Media Server, Jellyfin, Previewgenerator, Remotedesktop and Midnight Commander.\n Note: Vaultwarden, Plex Media Server and Jellyfin files were restored (if they were installed before) but the containers need to get \ diff --git a/not-supported/wireguard.sh b/not-supported/wireguard.sh new file mode 100644 index 0000000000..a406ad8c3f --- /dev/null +++ b/not-supported/wireguard.sh @@ -0,0 +1,448 @@ +#!/bin/bash + +# T&M Hansson IT AB © - 2026, https://www.hanssonit.se/ +# Copyright © 2021 Simon Lindner (https://github.com/szaimen) + +true +SCRIPT_NAME="WireGuard" +SCRIPT_EXPLAINER="WireGuard is a modern VPN protocol that is much faster and simpler than e.g. OpenVPN. +This script will set up a WireGuard VPN server to connect devices to your home network from everywhere. + +It uses wg-easy, which provides a web interface to manage your clients. +This is their official website: https://github.com/wg-easy/wg-easy + +This script installs WireGuard in a Docker container." +# shellcheck source=lib.sh +source /var/scripts/fetch_lib.sh + +# Check for errors + debug code and abort if something isn't right +# 1 = ON +# 0 = OFF +DEBUG=0 +debug_mode + +# Check if root +root_check + +# The port that the WireGuard VPN listens on. This one needs to be forwarded. +WIREGUARD_PORT=51820 +# The port that the wg-easy web interface listens on inside the container. +# It is only bound to 127.0.0.1 on the host, since Apache2 proxies it via https. +WIREGUARD_WEB_PORT=51821 +# The port that Apache2 listens on to proxy the web interface via https +WIREGUARD_PROXY_PORT=51822 +# The name of the docker network that the container runs in. +# wg-easy needs an own network with IPv6 enabled. +WIREGUARD_NETWORK=wg-easy + +# Check if already installed +if ! is_docker_running || ! docker ps -a --format "{{.Names}}" | grep -q "^wg-easy$" +then + # Ask for installing + install_popup "$SCRIPT_NAME" +else + # Ask for removal or reinstallation + reinstall_remove_menu "$SCRIPT_NAME" + # Removal + docker rm -f wg-easy &>/dev/null + docker network rm "$WIREGUARD_NETWORK" &>/dev/null + # Remove the Apache2 configuration + if [ -f "$SITES_AVAILABLE/wg-easy.conf" ] + then + a2dissite wg-easy.conf &>/dev/null + rm -f "$SITES_AVAILABLE/wg-easy.conf" + restart_webserver + fi + # Delete firewall entries + ufw delete allow "$WIREGUARD_PROXY_PORT/tcp" &>/dev/null + # Delete the leftover rule of former installations that added it for the VPN port + ufw delete allow "$WIREGUARD_PORT/udp" &>/dev/null + # The user-data is kept on purpose so that a reinstallation doesn't lose the clients + if [ "$REINSTALL_REMOVE" = "Uninstall" ] + then + msg_box "The WireGuard configuration and all your clients were NOT removed \ +and are still stored in the 'wg_easy' docker volume. + +If you want to delete them as well, e.g. to be able to start from scratch \ +if you install WireGuard again later on, please run the following command: +'sudo docker volume rm wg_easy' + +Please don't forget to close port $WIREGUARD_PORT/udp in your router again \ +if you don't need it anymore." + else + msg_box "Please note that the WireGuard configuration in the 'wg_easy' \ +docker volume will be kept, which means that all your current clients will \ +still work after the reinstallation. + +This also means that the admin password will stay the same as before, since \ +wg-easy only applies the initial password on a fresh installation. + +If you want to start from scratch instead, please abort this script now with 'CTRL+C' \ +and run the following command before running it again: +'sudo docker volume rm wg_easy'" + fi + # Show successful uninstall if applicable + removal_popup "$SCRIPT_NAME" +fi + +# Warn about running this on a public server +msg_box "Attention! + +This script opens a VPN entry point into your servers network. \ +Only continue if you understand the implications and keep it updated! + +It is only intended to be used on a server in a trusted home network. \ +Do NOT run this on a public VPS or any other server whose ip address is \ +directly reachable from the internet." + +if ! yesno_box_yes "Is this server running in a trusted home network?" +then + exit 1 +fi + +# Check for a leftover PiVPN installation from former versions of this script. +# It runs WireGuard on the host and occupies port $WIREGUARD_PORT UDP. +if [ -f /etc/pivpn/wireguard/setupVars.conf ] || command -v pivpn &>/dev/null +then + msg_box "It seems like PiVPN is still installed on this server. + +Former versions of this script used PiVPN to run WireGuard directly on the host. \ +It occupies port $WIREGUARD_PORT UDP, which means that the new WireGuard container \ +would not be able to start. + +You need to uninstall PiVPN first. You can do this by running the following commands: +'sudo pivpn uninstall' +'sudo rm -r /etc/wireguard /etc/pivpn' + +Please note that this will remove all your current PiVPN clients. You will have to \ +create them again with the new web interface afterwards. + +Please uninstall PiVPN and run this script again." + exit 1 +fi + +# wg-easy needs the wireguard kernel module on the host. +# It ships as a loadable module on all kernels since 5.6, but might not be loaded yet. +print_text_in_color "$ICyan" "Checking if the WireGuard kernel module is available..." +if ! lsmod | grep -q "^wireguard" +then + if ! modprobe wireguard &>/dev/null + then + msg_box "The WireGuard kernel module is not available on this server, \ +which means that the container would not be able to start. + +It ships as a loadable module on all common distributions with kernel 5.6 or \ +later. Your current kernel is: $(uname -r) + +Please install the WireGuard kernel module on this server first and \ +run this script again." + exit 1 + fi +fi + +# Make sure that the module gets loaded again after a reboot +if ! [ -f /etc/modules-load.d/wireguard.conf ] +then + echo "wireguard" > /etc/modules-load.d/wireguard.conf +fi + +# Automatically get the domain +if [ -f "$NCPATH/occ" ] +then + # Get the NCDOMAIN + NCDOMAIN=$(nextcloud_occ_no_check config:system:get overwrite.cli.url | sed 's|https://||;s|/||') + + # Check if Nextcloud is installed + if ! curl -s https://"$NCDOMAIN"/status.php | grep -q 'installed":true' || [ "$NCDOMAIN" = "nextcloud" ] + then + msg_box "It seems like Nextcloud is not installed or that you don't use https on: +$NCDOMAIN. + +Please install Nextcloud and make sure your domain is reachable, or activate TLS +on your domain to be able to run this script. + +We need this to make sure that the domain works for connections over WireGuard." + exit 1 + fi +fi + +# Ask for the domain +if ! [ -f "$NCPATH/occ" ] +then + # Enter the domain yourself + NCDOMAIN=$(input_box_flow "Please enter the domain that you want to use for WireGuard. +It should most likely point to your home ip address via DDNS.") +fi + +# Inform user to open the port +msg_box "To make WireGuard work, you will need to open port $WIREGUARD_PORT UDP \ +in your router and forward it to this server. + +Attention! The web interface on port $WIREGUARD_PROXY_PORT TCP must NOT be \ +forwarded, since it is only meant to be reachable inside your local network! + +You will have the option to automatically open port $WIREGUARD_PORT UDP by \ +using UPNP after the installation succeeded." + +if yesno_box_no "Do you want to use UPNP to open port $WIREGUARD_PORT UDP?" +then + # The forward is only created after the installation succeeded, so that a + # failing install doesn't leave an open port to a non-existing VPN server + USE_UPNP=yes +fi + +# Check the port +if ! yesno_box_yes "Unfortunately we are not able to check automatically if port \ +$WIREGUARD_PORT UDP is open. So please make sure to open it correctly! +Do you still want to continue?" +then + exit 1 +fi + +# Install Docker +install_docker + +# Generate a new admin password. wg-easy refuses logins for too short +# passwords, which is why we use 16 characters here. +PASSWORD=$(gen_passwd 16 "a-zA-Z0-9") + +# The INIT_* variables are only applied if no existing config is found, so a kept +# volume means the old password stays. An empty one is left by a failed install. +if docker volume ls --format "{{.Name}}" | grep -q "^wg_easy$" +then + WIREGUARD_VOLUME="$(docker volume inspect wg_easy --format '{{.Mountpoint}}' 2>/dev/null)" + if [ -n "$WIREGUARD_VOLUME" ] && [ -n "$(ls -A "$WIREGUARD_VOLUME" 2>/dev/null)" ] + then + EXISTING_CONFIG=yes + else + # Remove the leftovers so that wg-easy applies the INIT_* variables again + docker volume rm wg_easy &>/dev/null + fi +fi + +# Get the docker container +print_text_in_color "$ICyan" "Getting WireGuard..." +if ! docker pull ghcr.io/wg-easy/wg-easy:15 +then + msg_box "Failed to download the WireGuard container image. + +Please check your internet connection and report this issue here $ISSUES \ +if you can't solve it yourself." + exit 1 +fi + +# wg-easy needs an own docker network with IPv6 enabled since it hands out +# IPv6 addresses to its clients. The default bridge network doesn't support this. +if ! docker network ls --format "{{.Name}}" | grep -q "^$WIREGUARD_NETWORK$" +then + print_text_in_color "$ICyan" "Creating the WireGuard docker network..." + # The subnets are not set on purpose, so that docker picks free ones itself + # and we don't collide with the local network or other docker networks + if ! docker network create \ + --driver bridge \ + --ipv6 \ + "$WIREGUARD_NETWORK" + then + msg_box "Failed to create the WireGuard docker network. + +Please report this issue here $ISSUES if you can't solve it yourself." + exit 1 + fi +fi + +# Create WireGuard. The INIT_* variables set up the admin account on the first +# start only. 'INSECURE=true' is needed since wg-easy only serves plain http. +print_text_in_color "$ICyan" "Installing WireGuard..." +if ! docker run -d \ +--name wg-easy \ +--restart always \ +--network "$WIREGUARD_NETWORK" \ +-p "$WIREGUARD_PORT":"$WIREGUARD_PORT"/udp \ +-p 127.0.0.1:"$WIREGUARD_WEB_PORT":"$WIREGUARD_WEB_PORT"/tcp \ +--cap-add NET_ADMIN \ +--cap-add SYS_MODULE \ +--sysctl net.ipv4.ip_forward=1 \ +--sysctl net.ipv4.conf.all.src_valid_mark=1 \ +--sysctl net.ipv6.conf.all.disable_ipv6=0 \ +--sysctl net.ipv6.conf.all.forwarding=1 \ +--sysctl net.ipv6.conf.default.forwarding=1 \ +-e TZ="$(cat /etc/timezone)" \ +-e INSECURE=true \ +-e PORT="$WIREGUARD_WEB_PORT" \ +-e INIT_ENABLED=true \ +-e INIT_USERNAME=admin \ +-e INIT_PASSWORD="$PASSWORD" \ +-e INIT_HOST="$NCDOMAIN" \ +-e INIT_PORT="$WIREGUARD_PORT" \ +-v wg_easy:/etc/wireguard \ +-v /lib/modules:/lib/modules:ro \ +ghcr.io/wg-easy/wg-easy:15 +then + msg_box "Failed to create the WireGuard container. + +Please report this issue here $ISSUES if you can't solve it yourself." + # Remove the container leftovers so that this script can be run again + docker rm -f wg-easy &>/dev/null + exit 1 +fi + +# Add prune command +add_dockerprune + +# Install apache2 +install_if_not apache2 + +# Enable Apache2 module's +a2enmod headers +a2enmod rewrite +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod proxy_wstunnel + +# Only add TLS 1.3 on supported Ubuntu releases +if version "$SUPPORTED_VERSION_MIN" "$DISTRO" "$SUPPORTED_VERSION_MAX" +then + TLS13="+TLSv1.3" +fi + +# Create the vhost that proxies the wg-easy web interface via https. The cert is +# self-signed since the interface is only reachable in the local network. +cat << WIREGUARD_CONF > "$SITES_AVAILABLE/wg-easy.conf" +Listen $WIREGUARD_PROXY_PORT + + Header add Strict-Transport-Security: "max-age=15768000;includeSubdomains" + + # Intermediate configuration + SSLEngine on + SSLCompression off + SSLProtocol -all +TLSv1.2 $TLS13 + SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + SSLHonorCipherOrder off + SSLSessionTickets off + ServerSignature off + + # Logs + LogLevel warn + CustomLog \${APACHE_LOG_DIR}/access.log combined + ErrorLog \${APACHE_LOG_DIR}/error.log + + # Just in case - see below + SSLProxyEngine On + SSLProxyVerify None + SSLProxyCheckPeerCN Off + SSLProxyCheckPeerName Off + + # This is needed to redirect access on http://$ADDRESS:$WIREGUARD_PROXY_PORT/ + # to https://$ADDRESS:$WIREGUARD_PROXY_PORT/ + ErrorDocument 400 https://$ADDRESS:$WIREGUARD_PROXY_PORT/ + + # wg-easy uses websockets to keep the web interface up to date + RewriteEngine On + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteCond %{HTTP:Connection} upgrade [NC] + RewriteRule ^/?(.*) "ws://127.0.0.1:$WIREGUARD_WEB_PORT/\$1" [P,L] + + # basic proxy settings + ProxyRequests off + ProxyPass / "http://127.0.0.1:$WIREGUARD_WEB_PORT/" + ProxyPassReverse / "http://127.0.0.1:$WIREGUARD_WEB_PORT/" + ProxyPreserveHost On + +### LOCATION OF CERT FILES ### + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + +WIREGUARD_CONF + +# Enable config +check_command a2ensite wg-easy.conf + +# Restart webserver +if ! restart_webserver +then + msg_box "Apache2 could not restart... +The script will exit." + exit 1 +fi + +# Add firewall rules. The VPN port is published by the container, which docker +# opens in the nat table before ufw. A former PiVPN rule for it is stale now. +ufw delete allow "$WIREGUARD_PORT"/udp &>/dev/null +ufw allow "$WIREGUARD_PROXY_PORT"/tcp comment 'WireGuard Web' &>/dev/null + +# Check if the container is actually running, since it exits on startup +# if e.g. the wireguard kernel module cannot be loaded +countdown "Waiting for WireGuard to start... " 15 +if ! docker ps --format "{{.Names}}" | grep -q "^wg-easy$" +then + msg_box "The WireGuard container was created but is not running. + +These are the logs of the container: +$(docker logs --tail 20 wg-easy 2>&1) + +Please report this issue here $ISSUES if you can't solve it yourself." + exit 1 +fi + +# Now that the server actually runs, open the port in the router if chosen +if [ "$USE_UPNP" = "yes" ] +then + unset FAIL + open_port "$WIREGUARD_PORT" UDP + cleanup_open_port +fi + +# Inform the user about the successful installation +if [ "$EXISTING_CONFIG" = "yes" ] +then + # An existing configuration was found, which means that wg-easy ignored the + # initial admin account and kept the one from the previous installation + msg_box "Congratulations, your WireGuard server was set up correctly! + +The web interface is reachable inside your local network on: +https://$ADDRESS:$WIREGUARD_PROXY_PORT + +Since an existing WireGuard configuration was found in the 'wg_easy' docker \ +volume, your previous admin account and all your clients were kept. This means \ +that you need to log in with the same username and password as before. + +If you don't know your password anymore, you can start from scratch by running \ +the following commands and running this script again afterwards: +'sudo docker rm -f wg-easy' +'sudo docker volume rm wg_easy' + +Attention! This will delete all your clients as well. + +Please note that the certificate is self-signed, which means that your browser \ +will show a warning that you need to accept." +else + msg_box "Congratulations, your WireGuard server was set up correctly! + +The web interface is reachable inside your local network on: +https://$ADDRESS:$WIREGUARD_PROXY_PORT + +Username: admin +Password: $PASSWORD + +Please write down this password now! We cannot show it to you again later on, \ +since it is only used during the initial setup of the container. + +Please note that the certificate is self-signed, which means that your browser \ +will show a warning that you need to accept." +fi + +msg_box "How to add your devices: + +1. Visit https://$ADDRESS:$WIREGUARD_PROXY_PORT and log in +2. Create a new client for each of your devices +3. Scan the shown QR code with the WireGuard app on your phone, \ +or download the configuration file for your computer + +Attention! Every device needs its own client profile! + +Your clients will connect to '$NCDOMAIN' on port $WIREGUARD_PORT UDP. \ +You can change this host in the web interface if you want to connect \ +via a different address." + +exit diff --git a/not-supported/pivpn.sh b/old/pivpn.sh similarity index 100% rename from not-supported/pivpn.sh rename to old/pivpn.sh From d8fa1102345f66a62ef0336a50da428695cadf0b Mon Sep 17 00:00:00 2001 From: "Simon L." Date: Tue, 15 Sep 2026 10:57:49 +0200 Subject: [PATCH 2/3] add a workflow that updates the wg major version regularly Signed-off-by: Simon L. --- .../update-wireguard-major-version.yml | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/update-wireguard-major-version.yml diff --git a/.github/workflows/update-wireguard-major-version.yml b/.github/workflows/update-wireguard-major-version.yml new file mode 100644 index 0000000000..11b94160c9 --- /dev/null +++ b/.github/workflows/update-wireguard-major-version.yml @@ -0,0 +1,88 @@ +name: 'Update WireGuard Major Version' + +on: + schedule: + # Run on the first day of every month at 04:00 UTC + - cron: '0 4 1 * *' + workflow_dispatch: # Allow manual trigger + +jobs: + update-wireguard: + name: Update the pinned wg-easy major version + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Update the pinned major version + id: update + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CURRENT="$(grep -oP '(?<=ghcr\.io/wg-easy/wg-easy:)\d+' not-supported/wireguard.sh | head -1)" + if [ -z "$CURRENT" ] + then + echo "Could not find the pinned wg-easy version in not-supported/wireguard.sh" + exit 1 + fi + # The 'latest' endpoint never returns drafts or prereleases, + # which is important since wg-easy publishes beta releases as well + TAG="$(gh api repos/wg-easy/wg-easy/releases/latest --jq '.tag_name')" + # '|| true' since a non-matching grep would otherwise abort the step + # before the error message below can run, as GitHub runs this with 'bash -e' + LATEST="$(echo "$TAG" | grep -oP '(?<=^v)\d+' || true)" + if [ -z "$LATEST" ] + then + echo "Could not parse a major version from the tag '$TAG'" + exit 1 + fi + # Compare numerically here so that e.g. 9 -> 10 is handled correctly + if [ "$LATEST" -le "$CURRENT" ] + then + echo "The pinned version $CURRENT is already up to date" + exit 0 + fi + # Make sure that we don't pin a tag that isn't published on ghcr.io, + # since the release and the image don't necessarily appear at once + if ! docker manifest inspect "ghcr.io/wg-easy/wg-easy:$LATEST" &>/dev/null + then + echo "The image tag '$LATEST' doesn't exist (yet) on ghcr.io" + exit 0 + fi + sed -i "s|ghcr.io/wg-easy/wg-easy:$CURRENT|ghcr.io/wg-easy/wg-easy:$LATEST|g" not-supported/wireguard.sh + if ! git diff --quiet . + then + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "version=$LATEST" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create Pull Request + if: steps.update.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v8 + with: + commit-message: 'Update WireGuard to major version ${{ steps.update.outputs.version }}' + title: 'Update WireGuard to major version ${{ steps.update.outputs.version }}' + body: | + Automated update of the pinned wg-easy image from major version `${{ steps.update.outputs.current }}` to `${{ steps.update.outputs.version }}` (latest release: `${{ steps.update.outputs.tag }}`). + + The image in `not-supported/wireguard.sh` is pinned to a major tag so that `nextcloud_update.sh` only pulls patch releases automatically. Major upgrades are intentionally not automatic and need this PR. + + > [!WARNING] + > Please **do not merge this without testing it first**. Major releases of wg-easy have contained breaking changes in the past, which can affect: + > - the `INIT_*` environment variables that create the admin account + > - the format of the config in the `wg_easy` docker volume, which existing installs keep across a reinstall + > - the sysctls, capabilities and the web interface port + > + > Please check the [release notes](https://github.com/wg-easy/wg-easy/releases/tag/${{ steps.update.outputs.tag }}) and run a fresh install **and** a reinstall over an existing volume before merging. + branch: 'automated/wireguard-version' + delete-branch: true + labels: | + automated + maintenance + reviewers: 'szaimen' + assignees: 'szaimen' From 371ea34bb3b2fae6856219a2a04bcf53fb4358b6 Mon Sep 17 00:00:00 2001 From: "Simon L." Date: Tue, 15 Sep 2026 15:39:46 +0200 Subject: [PATCH 3/3] try to fix unbound Signed-off-by: Simon L. --- not-supported/pi-hole.sh | 104 +++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/not-supported/pi-hole.sh b/not-supported/pi-hole.sh index f6afdd0e0a..266c5bcbe5 100644 --- a/not-supported/pi-hole.sh +++ b/not-supported/pi-hole.sh @@ -75,12 +75,14 @@ else if [ -f /etc/unbound/unbound.conf.d/pi-hole.conf ] then rm -f /etc/unbound/unbound.conf.d/pi-hole.conf + # Remove the daily restart cron job + crontab -u root -l 2>/dev/null | grep -v "restart unbound" | crontab -u root - rm -f /etc/systemd/system/unbound.service.d/ncvm-pihole.conf rmdir /etc/systemd/system/unbound.service.d &>/dev/null systemctl daemon-reload - if is_this_installed unbound + if is_this_installed unbound || is_this_installed unbound-anchor then - apt-get purge unbound -y + apt-get purge unbound unbound-anchor -y apt-get autoremove -y fi fi @@ -376,11 +378,36 @@ ufw allow "$PIHOLE_PROXY_PORT"/tcp comment 'Pi-hole Web' &>/dev/null if [ "$UNBOUND" = "yes" ] then # Install unbound. We do not use install_if_not here, since it installs - # with RUNLEVEL=1, which skips the postinst that creates the DNSSEC anchor. - if ! is_this_installed unbound + # with RUNLEVEL=1, which skips parts of the package setup. + if ! is_this_installed unbound || ! is_this_installed unbound-anchor then apt-get update -q4 & spinner_loading - check_command apt-get install unbound -y + check_command apt-get install unbound unbound-anchor -y + fi + + # Ubuntu makes unbound listen on 127.0.0.1:53 via resolvconf, which + # conflicts with port 53 that the Pi-hole container publishes + systemctl disable --now unbound-resolvconf.service &>/dev/null + rm -f /etc/unbound/unbound.conf.d/resolvconf_resolvers.conf + + # The DNSSEC root trust anchor is not always created by the package, + # but unbound refuses to start without it + if ! [ -f /var/lib/unbound/root.key ] + then + print_text_in_color "$ICyan" "Creating the DNSSEC root trust anchor..." + mkdir -p /var/lib/unbound + # It returns 1 when it had to bootstrap the key from its built-in + # copy, which is the expected case on a fresh installation + unbound-anchor -a /var/lib/unbound/root.key || true + chown unbound:unbound /var/lib/unbound/root.key &>/dev/null + if ! [ -f /var/lib/unbound/root.key ] + then + msg_box "Could not create the DNSSEC root trust anchor in \ +'/var/lib/unbound/root.key', which means that unbound cannot start. + +Please report this to $ISSUES" + exit 1 + fi fi # unbound listens on the docker bridge gateway so that the container can @@ -442,25 +469,61 @@ After=docker.service UNBOUND_SERVICE systemctl daemon-reload - # Restart unbound + # Allow the container to reach unbound on the docker bridge + ufw allow in on docker0 to "$DOCKER_GATEWAY" port 5335 comment 'Pi-hole unbound' &>/dev/null + + # Restart unbound. A former failed start can latch the unit into a failed + # state with 'start request repeated too quickly', which we clear first print_text_in_color "$ICyan" "Restarting unbound..." - check_command systemctl restart unbound - countdown "Waiting for unbound to start... " 10 + systemctl reset-failed unbound &>/dev/null + systemctl restart unbound &>/dev/null + + # Wait for unbound to actually answer instead of guessing a delay, since + # a restart can still end in a failed unit or a not yet ready resolver + UNBOUND_READY=no + for _ in $(seq 1 30) + do + if docker exec pihole dig +time=2 +tries=1 @"$DOCKER_GATEWAY" -p 5335 \ +nextcloud.com &>/dev/null + then + UNBOUND_READY=yes + break + fi + sleep 1 + done + if [ "$UNBOUND_READY" != "yes" ] + then + msg_box "unbound did not start correctly and does not answer queries. + +Please report this to $ISSUES" + exit 1 + fi - # Testing DNSSEC - install_if_not dnsutils - if ! dig sigfail.verteiltesysteme.net @"$DOCKER_GATEWAY" -p 5335 | grep -q "SERVFAIL" + # Testing DNSSEC from inside the container, since unbound refuses queries + # from the host. A validated answer carries the 'ad' flag. + if ! docker exec pihole dig +time=10 +tries=1 @"$DOCKER_GATEWAY" -p 5335 \ +sigok.verteiltesysteme.net | grep -q "flags:.* ad[;,]" then - msg_box "Something went wrong while testing SERVFAIL. + msg_box "Something went wrong while testing DNSSEC validation. +unbound did not return an authenticated answer for a signed domain. + Please report this to $ISSUES" - elif ! dig sigok.verteiltesysteme.net @"$DOCKER_GATEWAY" -p 5335 | grep -q "NOERROR" + # A domain with a broken signature must not resolve. unbound either answers + # with SERVFAIL or doesn't answer at all while it retries the nameservers + elif docker exec pihole dig +time=10 +tries=1 @"$DOCKER_GATEWAY" -p 5335 \ +sigfail.verteiltesysteme.net | grep -q "flags:.* ad[;,]" then - msg_box "Something went wrong while testing NOERROR. + msg_box "Something went wrong while testing DNSSEC validation. +unbound validated a domain with a broken signature. + Please report this to $ISSUES" fi - # Allow the container to reach unbound on the docker bridge - ufw allow in on docker0 to "$DOCKER_GATEWAY" port 5335 comment 'Pi-hole unbound' &>/dev/null + # Restart unbound daily, since a failed start at boot latches the unit and + # would leave the Pi-hole without its upstream DNS server until fixed by hand + crontab -u root -l 2>/dev/null | grep -v "restart unbound" | crontab -u root - + crontab -u root -l 2>/dev/null | { cat; echo "0 4 * * * systemctl reset-failed \ +unbound && systemctl restart unbound"; } | crontab -u root - # Configure Pi-hole to use unbound as its upstream DNS server print_text_in_color "$ICyan" "Configuring Pi-hole to use unbound..." @@ -530,13 +593,4 @@ which will generate and show you a new password while keeping all your settings. Please also note that the DHCP functionality of Pi-hole is not enabled since the \ container doesn't run in the host network." -# Inform about updates -msg_box "Concerning updates: -Pi-hole runs in a Docker container, which means that you can update it \ -by running the following commands: -'sudo docker pull pihole/pihole:latest' -and afterwards running this script again and choosing 'Reinstall'. - -Your settings and statistics in '$PIHOLE_DIR' will be kept in that process." - exit