Skip to content

Instantly share code, notes, and snippets.

@freefirex
Created February 14, 2025 19:18
Show Gist options
  • Save freefirex/95e4dc0f93388d620c26d91371b8f1dc to your computer and use it in GitHub Desktop.
Save freefirex/95e4dc0f93388d620c26d91371b8f1dc to your computer and use it in GitHub Desktop.
softether vpn setup script
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "This script must be run as root"
exit
fi
# Function to prompt for input and ensure it's not blank (except for domain)
prompt_input() {
local var_name="$1"
local prompt_text="$2"
local allow_empty="$3"
local input_value
while true; do
read -rp "$prompt_text: " input_value
if [[ -z "$input_value" && "$allow_empty" == "no" ]]; then
echo "Error: $var_name cannot be blank. Please enter a value."
else
eval "$var_name=\"$input_value\""
break
fi
done
}
prompt_input "adminpassword" "Enter admin password" "no"
prompt_input "vpnuser" "Enter VPN username" "no"
prompt_input "vpnpwd" "Enter VPN password" "no"
prompt_input "domain" "Enter domain (leave blank for none)" "yes"
while true; do
prompt_input "interface" "Enter network interface we will bridge to" "no"
if ip link show "$interface" > /dev/null 2>&1; then
break
else
echo "Error: Interface '$interface' does not exist. Please enter a valid interface."
fi
done
prompt_input "tapname" "Enter TAP name" "no"
while true; do
prompt_input "configiptables" "Should this script configure iptables for forwarding? (y/n)" "no"
if [[ "$configiptables" == "y" || "$configiptables" == "n" ]]; then
break
else
echo "Error: answer must be 'y' or 'n' alone."
fi
done
prompt_input "vpnnet" "Enter the cidr based range for the vpn network. ex. 192.168.30.0/24" "no"
vpnbase=${vpnnet%%/*}
vpncidr=${vpnnet##*/}
prompt_input "vpngateway" "Enter the ip within the cidr range the vpn should use as its gateway ex. 192.168.30.1" "no"
prompt_input "vpnnetmask" "Please enter the 4 octet netmask as given by your cider in vpnnet. ex. 255.255.255.0" "no"
prompt_input "dhcpstart" "Please enter the start of the dhcp server range ie. 192.168.30.100" "no"
prompt_input "dhcpend" "Please enter the end of the dhcp server range ie. 192.168.30.200" "no"
echo "\nConfiguration:"
echo "Admin Password: [HIDDEN]"
echo "VPN User: $vpnuser"
echo "VPN Password: [HIDDEN]"
echo "Domain: ${domain:-"(none)"}"
echo "Network Interface: $interface"
echo "TAP Name: $tapname"
echo "Configuring iptables $configiptables"
echo "VPN network: $vpnnet"
echo "VPN gateway: $vpngateway"
echo "VPN netmask: $vpnnetmask"
echo "DHCP range: $dhcpstart - $dhcpend"
while true; do
prompt_input "continue" "Is this configuration correct? (y/n)" "no"
if [[ "$continue" == "y" ]]; then
break
elif [[ "$continue" == "n" ]]; then
exit
else
echo "Error: answer must be 'y' or 'n' alone."
fi
done
tapinterface="tap_$tapname"
#install dependencies
function install_dependencies {
apt update && \
apt install -y cmake isc-dhcp-server gcc certbot git g++ make pkgconf libncurses5-dev libssl-dev libsodium-dev libreadline-dev zlib1g-dev && \
apt clean
systemctl disable isc-dhcp-server
systemctl stop isc-dhcp-server
}
#clone vpn package at tag and build it, removing git history
function install_softether {
if command -v vpnserver > /dev/null 2>&1; then
echo "vpnserver appears to already be installed, skipping"
return
fi
git clone -b 5.02.5187 --depth 1 --recurse-submodules https://github.com/SoftEtherVPN/SoftEtherVPN.git && \
cd SoftEtherVPN && \
find . -type d -name ".git" -exec rm -rf {} + && \
CMAKE_FLAGS="-DSE_PIDDIR=/run/softether -DSE_LOGDIR=/var/log/softether -DSE_DBDIR=/var/lib/softether" ./configure && \
make -C build
make -C build install
mkdir -p /run/softether
mkdir -p /var/log/softether
mkdir -p /var/lib/softether
cd /root
}
#if domain was specified, get ssl cert
function get_cert {
if [ ! -f /etc/letsencrypt/live/$domain/fullchain.pem ]; then
certbot certonly -d $domain --standalone --agree-tos --register-unsafely-without-email
if [ ! -f /etc/letsencrypt/live/$domain/fullchain.pem ]; then
echo "Failed to get Cert for domain $domain, correct domain in script and retry"
exit
fi
fi
}
#perform initial server configuration
function initial_setup {
vpnserver start
sleep 10
#create user / pwd
vpncmd 127.0.0.1 /SERVER /hub:default /cmd UserCreate $vpnuser /Group:none /RealName:none /Note:none
vpncmd 127.0.0.1 /SERVER /hub:default /cmd UserPasswordSet $vpnuser /PASSWORD:$vpnpwd
#disable securenat since we will use a local bridge
vpncmd 127.0.0.1 /SERVER /hub:default /cmd SecureNatDisable
vpncmd 127.0.0.1 /SERVER /cmd BridgeCreate default /DEVICE:$tapname /TAP:yes
#use cert we got from letsencrypt if domain name was given
if [[ -n "$domain" ]]; then
vpncmd 127.0.0.1 /SERVER /hub:none /cmd ServerCertSet /LOADCERT:/etc/letsencrypt/live/$domain/fullchain.pem /LOADKEY:/etc/letsencrypt/live/$domain/privkey.pem
fi
#finally lets set passwords on the admin / hub
vpncmd 127.0.0.1 /SERVER /hub:none /cmd ServerPasswordSet $adminpassword
vpncmd 127.0.0.1 /SERVER /hub:default /cmd SetHubPassword $adminpassword
}
#Configure IP tables to forward traffic
function config_iptables {
iptables -A FORWARD -s $vpnnet -j ACCEPT
iptables -A FORWARD -d $vpnnet -j ACCEPT
iptables -t nat -A POSTROUTING -s $vpnnet -o $interface -j MASQUERADE
iptables-save > /root/nat.conf
echo "saving iptables to /root/nat.conf"
}
#enable ip forwarding, both now and for reboots
function config_forwarding {
sysctl -w net.ipv4.ip_forward=1
# Check if the setting is already in /etc/sysctl.conf
if grep -q "^net.ipv4.ip_forward=" /etc/sysctl.conf; then
echo "Updating existing net.ipv4.ip_forward entry..."
sudo sed -i 's/^net.ipv4.ip_forward=.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf
else
echo "Appending net.ipv4.ip_forward=1 to /etc/sysctl.conf..."
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
fi
}
#configure dhcp to server on tap interfacce
function config_dhcp {
#write out our tap interface as the one to bind
# File to modify
local dhcp_config="/etc/default/isc-dhcp-server"
local dhcp_block=$(cat << EOF
option classless-win code 249 = array of unsigned integer 8;
subnet $vpnbase netmask $vpnnetmask {
range $dhcpstart $dhcpend;
option routers $vpngateway;
option subnet-mask $vpnnetmask;
option domain-name-servers 1.1.1.1;
}
EOF
)
# Comment out INTERFACESv6 line if present
sed -i 's/^\(INTERFACESv6=.*\)$/#\1/' "$dhcp_config"
# Update INTERFACESv4 with the new tap interface
sed -i "s|^INTERFACESv4=.*|INTERFACESv4=\"$tapinterface\"|" "$dhcp_config"
echo "$dhcp_block" >> /etc/dhcp/dhcpd.conf
echo "updated dhcp server settings"
}
#set up script to restart server on reboot
function setup_autorun_script {
echo "mkdir -p /run/softether" > /root/vpnstartup.sh
echo "mkdir -p /var/log/softether" >> /root/vpnstartup.sh
echo "mkdir -p /var/lib/softether" >> /root/vpnstartup.sh
echo "vpnserver start" >> /root/vpnstartup.sh
echo "sleep 10" >> /root/vpnstartup.sh
echo "ip addr add $vpngateway/$vpncidr dev $tapinterface" >> /root/vpnstartup.sh
if [[ $configiptables == "y" ]]; then
echo "iptables-restore /root/nat.conf" >> /root/vpnstartup.sh
fi
echo "sleep 1" >> /root/vpnstartup.sh
echo "systemctl start isc-dhcp-server" >> /root/vpnstartup.sh
echo "Created Auto startup script at /root/vpnstartup.sh"
}
#configure systemctl to call startup script
function setup_systemctl {
local systemctl=$(cat << EOF
[Unit]
Description=Start softether vpn server and apply ip
After=network.target
[Service]
Type=simple
ExecStart=/bin/bash /root/vpnstartup.sh
RemainAfterExit=yes
User=root
[Install]
WantedBy=multi-user.target
EOF
)
echo "$systemctl" > /etc/systemd/system/vpnserver.service
systemctl daemon-reload
systemctl enable vpnserver.service
echo "installed and enabled systemctl startup script"
}
#we are using root as our working directory
cd /root
install_dependencies
install_softether
if [[ -n "$domain" ]]; then
get_cert
fi
initial_setup
if [[ "$configiptables" == "y" ]]; then
config_iptables
fi
config_forwarding
config_dhcp
setup_autorun_script
setup_systemctl
#now do our initial start here
ip addr add $vpngateway/$vpncidr dev $tapinterface
systemctl restart isc-dhcp-server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment