Last active
June 9, 2025 20:06
-
-
Save sprytnyk/c0e72172354f9481d5c183905dc9dcb5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# VPN bootstrap script. | |
# 1st step | |
# Abort script at first error, when a command exits with non-zero status | |
# (except in until or while loops, if-tests, list constructs) | |
# https://www.tldp.org/LDP/abs/html/options.html | |
set -eu | |
# update repositories and install libraries | |
apt update && apt upgrade -y && apt install -y make git pwgen htop lnav | |
apt install -y unbound > /dev/null 2>&1 | |
# configure locale | |
locale-gen en_GB.UTF-8 | |
locale-gen en_US.UTF-8 | |
# create a user and add to sudo group | |
echo "Enter your user name:" | |
read -r USER_NAME | |
adduser --disabled-password --gecos "" "${USER_NAME}" | |
usermod -aG sudo "${USER_NAME}" | |
# set a password | |
USER_PASSWORD="$(pwgen -s 20 -y)" | |
echo -e "Your password is \e[34m\"${USER_PASSWORD}\"\e[0m. \e[91mDON'T FORGET TO SAVE IT!\e[0m" | |
usermod --password "$(openssl passwd -1 "${USER_PASSWORD}")" "${USER_NAME}" | |
# sync root .ssh directory with vald user | |
rsync --archive --chown="${USER_NAME}":"${USER_NAME}" ~/.ssh "/home/${USER_NAME}" | |
# setup sshd | |
sed -i "0,/PermitRootLogin yes/s//PermitRootLogin no/" /etc/ssh/sshd_config | |
sed -i "0,/#PasswordAuthentication yes/s//PasswordAuthentication no/" /etc/ssh/sshd_config | |
sed -i "0,/#PermitEmptyPasswords no/s//PermitEmptyPasswords no/" /etc/ssh/sshd_config | |
systemctl restart ssh | |
# Add local DNS resolvers to /etc/hosts | |
# to be able to see them in "Queries answered by" | |
cat >> /etc/hosts <<EOF | |
# Local DNS resolvers | |
127.1.1.2 unbound | |
EOF | |
# Download the current root hints file (the list of primary root servers which are serving the domain "." | |
# - the root domain). Update it roughly every six months. Note that this file changes infrequently. | |
wget -O root.hints https://www.internic.net/domain/named.root | |
sudo mv root.hints /var/lib/unbound/ | |
# Add Unbound conf | |
cat > /etc/unbound/unbound.conf.d/pi-hole.conf <<EOF | |
server: | |
# If no logfile is specified, syslog is used | |
# logfile: "/var/log/unbound/unbound.log" | |
verbosity: 3 | |
interface: 127.1.1.2 | |
port: 5353 | |
do-ip4: yes | |
do-udp: yes | |
do-tcp: yes | |
# May be set to yes if you have IPv6 connectivity | |
do-ip6: no | |
# You want to leave this to no unless you have *native* IPv6. With 6to4 and | |
# Terredo tunnels your web browser should favor IPv4 for the same reasons | |
prefer-ip6: no | |
# Use this only when you downloaded the list of primary root servers! | |
# If you use the default dns-root-data package, unbound will find it automatically | |
root-hints: "/var/lib/unbound/root.hints" | |
access-control: 127.0.0.0/8 allow | |
access-control: 0.0.0.0/0 deny | |
access-control: ::1 allow | |
access-control: ::0/0 deny | |
hide-identity: yes | |
hide-version: yes | |
# Trust glue only if it is within the server's authority | |
harden-glue: yes | |
# Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS | |
harden-dnssec-stripped: yes | |
# Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes | |
# see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details | |
use-caps-for-id: no | |
# Reduce EDNS reassembly buffer size. | |
# IP fragmentation is unreliable on the Internet today, and can cause | |
# transmission failures when large DNS messages are sent via UDP. Even | |
# when fragmentation does work, it may not be secure; it is theoretically | |
# possible to spoof parts of a fragmented DNS message, without easy | |
# detection at the receiving end. Recently, there was an excellent study | |
# >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<< | |
# by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/) | |
# in collaboration with NLnet Labs explored DNS using real world data from the | |
# the RIPE Atlas probes and the researchers suggested different values for | |
# IPv4 and IPv6 and in different scenarios. They advise that servers should | |
# be configured to limit DNS messages sent over UDP to a size that will not | |
# trigger fragmentation on typical network links. DNS servers can switch | |
# from UDP to TCP when a DNS response is too big to fit in this limited | |
# buffer size. This value has also been suggested in DNS Flag Day 2020. | |
edns-buffer-size: 1232 | |
# Perform prefetching of close to expired message cache entries | |
# This only applies to domains that have been frequently queried | |
prefetch: yes | |
# One thread should be sufficient, can be increased on beefy machines. In reality for most users running on small networks or on a single machine, it should be unnecessary to seek performance enhancement by increasing num-threads above 1. | |
num-threads: 1 | |
# Ensure kernel buffer is large enough to not lose messages in traffic spikes | |
so-rcvbuf: 1m | |
# Ensure privacy of local IP ranges | |
private-address: 192.168.0.0/16 | |
private-address: 169.254.0.0/16 | |
private-address: 172.16.0.0/12 | |
private-address: 10.0.0.0/8 | |
private-address: fd00::/8 | |
private-address: fe80::/10 | |
EOF | |
mkdir -p /etc/dnsmasq.d | |
echo 'edns-packet-max=1232' > /etc/dnsmasq.d/99-edns.conf | |
# restart unbound and bypass failed to start Unbound DNS server error. | |
sleep 5 | |
systemctl restart unbound | |
# logging for unbound | |
mkdir -p /var/log/unbound | |
touch /var/log/unbound/unbound.log | |
chown unbound /var/log/unbound/unbound.log | |
# checks | |
dig pi-hole.net @127.1.1.2 -p 5353 | |
dig sigfail.verteiltesysteme.net @127.1.1.2 -p 5353 | |
dig sigok.verteiltesysteme.net @127.1.1.2 -p 5353 | |
# shows how unbound is doing | |
systemctl status unbound.service |
3rd step
$ touch /lib/systemd/system/hints.timer
[Unit]
Description=Download root hints monthly
[Timer]
OnCalendar=monthly
RandomizedDelaySec=2h
Persistent=true
[Install]
WantedBy=timers.target
$ touch /lib/systemd/system/hints.service
[Unit]
Description=Hints
[Service]
Type=oneshot
ExecStart=/root/.scripts/update-hints.sh
PrivateTmp=true
$ mkdir ~/.scripts && touch /root/.scripts/update-hints.sh
#!/usr/bin/env sh
# Download root hints
wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
$ systemctl restart unbound && systemctl enable hints.timer && systemctl start hints.timer && systemctl status hints.timer
to set pihole password
pihole setpassword
#!/bin/bash
# VPN bootstrap script for Debian 12
# Step 1: System preparation and Unbound DNS setup
set -eu
trap 'echo "β Error on line $LINENO"; exit 1' ERR
echo "π Starting VPN bootstrap setup..."
# Ensure sudo exists and update system
echo "π§ Updating system and installing base packages..."
apt-get update
apt-get install -y sudo
apt-get upgrade -y
# Install necessary tools
apt-get install -y make git pwgen htop lnav wget curl openssl rsync ufw \
gnupg software-properties-common locales unbound
# Configure locale (en_GB)
echo "π Configuring locales..."
sed -i '/en_GB.UTF-8/s/^# //' /etc/locale.gen
locale-gen
update-locale LANG=en_GB.UTF-8
# Create a user and add to sudo group
echo "π€ Enter your desired username:"
read -r USER_NAME
if id "${USER_NAME}" &>/dev/null; then
echo "β οΈ User '${USER_NAME}' already exists!"
else
adduser --disabled-password --gecos "" "${USER_NAME}"
usermod -aG sudo "${USER_NAME}"
echo "β
User '${USER_NAME}' created and added to sudo group."
fi
# Set secure password
USER_PASSWORD="$(pwgen -r ',;' -s 25 -y)"
echo -e "π Generated password for ${USER_NAME}: \e[34m${USER_PASSWORD}\e[0m. \e[91mSave it securely!\e[0m"
echo "${USER_NAME}:${USER_PASSWORD}" | chpasswd
# Sync SSH config
echo "π Syncing SSH keys from root to /home/${USER_NAME}/.ssh..."
rsync --archive --chown="${USER_NAME}:${USER_NAME}" ~/.ssh "/home/${USER_NAME}"
# Harden SSH configuration
echo "π Configuring SSH..."
sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
if grep -q "^#\?PasswordAuthentication" /etc/ssh/sshd_config; then
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
else
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
fi
if grep -q "^#\?PermitEmptyPasswords" /etc/ssh/sshd_config; then
sed -i 's/^#\?PermitEmptyPasswords.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config
else
echo "PermitEmptyPasswords no" >> /etc/ssh/sshd_config
fi
rm -f /etc/ssh/sshd_config.d/50-cloud-init.conf
systemctl restart ssh
# Setup /etc/hosts entry for Unbound
echo "π Adding Unbound to /etc/hosts..."
echo -e "\n# Local DNS resolvers\n127.1.1.2 unbound" >> /etc/hosts
# Download root hints file
echo "π Downloading root hints file..."
wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
# Configure Unbound
echo "π οΈ Configuring Unbound DNS resolver..."
cat > /etc/unbound/unbound.conf.d/pi-hole.conf <<EOF
server:
verbosity: 3
interface: 127.1.1.2
port: 5353
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
prefer-ip6: no
root-hints: "/var/lib/unbound/root.hints"
access-control: 127.0.0.0/8 allow
access-control: ::1 allow
access-control: 0.0.0.0/0 deny
access-control: ::0/0 deny
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
edns-buffer-size: 1232
prefetch: yes
num-threads: 1
so-rcvbuf: 1m
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: fd00::/8
private-address: fe80::/10
EOF
# Ensure dnsmasq doesn't break EDNS
mkdir -p /etc/dnsmasq.d
echo 'edns-packet-max=1232' > /etc/dnsmasq.d/99-edns.conf
# Start Unbound and wait
echo "π Restarting Unbound..."
sleep 5
systemctl restart unbound
# Setup Unbound logging (optional)
mkdir -p /var/log/unbound
touch /var/log/unbound/unbound.log
chown unbound /var/log/unbound/unbound.log
# Verification
echo "π Running test DNS queries..."
dig pi-hole.net @127.1.1.2 -p 5353
dig sigfail.verteiltesysteme.net @127.1.1.2 -p 5353
dig sigok.verteiltesysteme.net @127.1.1.2 -p 5353
# Show Unbound status
systemctl status unbound.service
echo "β
VPN bootstrap setup completed successfully."
Enable forwarding in /etc/sysctl.conf
with setting net.ipv4.ip_forward=1
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
$ sh -c "$(curl -fsSL https://gist.githubusercontent.com/sprytnyk/c0e72172354f9481d5c183905dc9dcb5/raw/a9ff69bcf26dbabe03a9880a5082da4aed7963dc/vpn-boostrap.sh)"
2nd step
install iptables-persistent
apt install iptables-persistent