11#! /bin/bash
22
33# Debian and Ubuntu Server Hardening Interactive Script
4- # Version: 0.80.1 | 2026-02-28
4+ # Version: 0.80.2 | 2026-03-01
55# Changelog:
6+ # - v0.80.2: Added an optional install of netbird (https://netbird.io/) as an alternative to tailscale.
67# - v0.80.1: Added a safety check to trigger the SSH rollback function if user is disconnected during SSH port change, preventing lockout.
78# Implement a check for a validated ssh key for the sudo user before revoking root access.
89# Perform changes to sshd config in a low-lexical-order file (10-hardening.conf) to minimize risk of conflicts with existing provider configs.
102103set -euo pipefail
103104
104105# --- Update Configuration ---
105- CURRENT_VERSION=" 0.80.1 "
106+ CURRENT_VERSION=" 0.80.2 "
106107SCRIPT_URL=" https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh"
107108CHECKSUM_URL=" ${SCRIPT_URL} .sha256"
108109
@@ -261,7 +262,7 @@ print_header() {
261262 printf ' %s\n' " ${CYAN} ╔═════════════════════════════════════════════════════════════════╗${NC} "
262263 printf ' %s\n' " ${CYAN} ║ ║${NC} "
263264 printf ' %s\n' " ${CYAN} ║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC} "
264- printf ' %s\n' " ${CYAN} ║ v0.80.1 | 2026-02-28 ║${NC} "
265+ printf ' %s\n' " ${CYAN} ║ v0.80.2 | 2026-03-01 ║${NC} "
265266 printf ' %s\n' " ${CYAN} ║ ║${NC} "
266267 printf ' %s\n' " ${CYAN} ╚═════════════════════════════════════════════════════════════════╝${NC} "
267268 printf ' \n'
@@ -1459,14 +1460,16 @@ sysinfo() {
14591460 local ip_addr public_ipv4 public_ipv6
14601461
14611462 # Try to get public IPv4 first
1462- public_ipv4=$(curl -4 -s -m 2 --connect-timeout 1 https://checkip.amazonaws.com 2>/dev/null || \
1463- curl -4 -s -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
1464- curl -4 -s -m 2 --connect-timeout 1 https://api.ipify.org 2>/dev/null)
1463+ public_ipv4=$(curl -4 -sf -m 2 --connect-timeout 1 https://ip.wiredalter.com 2>/dev/null || \
1464+ curl -4 -sf -m 2 --connect-timeout 1 https://checkip.amazonaws.com 2>/dev/null || \
1465+ curl -4 -sf -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
1466+ curl -4 -sf -m 2 --connect-timeout 1 https://api.ipify.org 2>/dev/null)
14651467 # If no IPv4, try IPv6
14661468 if [ -z "$public_ipv4" ]; then
1467- public_ipv6=$(curl -6 -s -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
1468- curl -6 -s -m 2 --connect-timeout 1 https://icanhazip.co 2>/dev/null || \
1469- curl -6 -s -m 2 --connect-timeout 1 https://api64.ipify.org 2>/dev/null)
1469+ public_ipv6=$(curl -6 -sf -m 2 --connect-timeout 1 https://ip.wiredalter.com 2>/dev/null || \
1470+ curl -6 -sf -m 2 --connect-timeout 1 https://ipconfig.io 2>/dev/null || \
1471+ curl -6 -sf -m 2 --connect-timeout 1 https://icanhazip.co 2>/dev/null || \
1472+ curl -6 -sf -m 2 --connect-timeout 1 https://api64.ipify.org 2>/dev/null)
14701473 fi
14711474 # Get local/internal IP as fallback
14721475 for iface in eth0 ens3 enp0s3 enp0s6 wlan0 ens33 eno1; do
@@ -1739,9 +1742,9 @@ alias top10='ps aux --sort=-%mem | head -n 11'
17391742
17401743# Quick network info.
17411744# Get public IP with timeouts (3s), fallbacks, and newline formatting
1742- alias myip='curl -s --connect-timeout 3 ip.me || curl -s --connect-timeout 3 icanhazip.com || curl -s --connect-timeout 3 ifconfig.me; echo'
1743- alias myip4='curl -4 -s --connect-timeout 3 ip.me || curl -4 -s --connect-timeout 3 icanhazip.com || curl -4 -s --connect-timeout 3 ifconfig.me; echo'
1744- alias myip6='curl -6 -s --connect-timeout 3 ip.me || curl -6 -s --connect-timeout 3 icanhazip.com || curl -6 -s --connect-timeout 3 ifconfig.me; echo'
1745+ alias myip='curl -sf --connect-timeout 3 ip.me || curl -sf --connect-timeout 3 icanhazip.com || curl -sf --connect-timeout 3 ifconfig.me; echo'
1746+ alias myip4='curl -4 -sf --connect-timeout 3 ip.me || curl -4 -sf --connect-timeout 3 icanhazip.com || curl -4 -sf --connect-timeout 3 ifconfig.me; echo'
1747+ alias myip6='curl -6 -sf --connect-timeout 3 ip.me || curl -6 -sf --connect-timeout 3 icanhazip.com || curl -6 -sf --connect-timeout 3 ifconfig.me; echo'
17451748# Show local IP address(es), excluding loopback.
17461749localip() {
17471750 ip -4 addr | awk '/inet/ {print $2}' | cut -d/ -f1 | grep -v '127.0.0.1'
@@ -2892,14 +2895,16 @@ collect_config() {
28922895 LOCAL_IP_V4=" "
28932896 fi
28942897 # 2. Get Public IPs with timeouts
2895- SERVER_IP_V4=$( curl -4 -s --connect-timeout 4 --max-time 5 https://ifconfig.me 2> /dev/null || \
2896- curl -4 -s --connect-timeout 4 --max-time 5 https://ip.me 2> /dev/null || \
2897- curl -4 -s --connect-timeout 4 --max-time 5 https://icanhazip.com 2> /dev/null || \
2898+ SERVER_IP_V4=$( curl -4 -sf --connect-timeout 4 --max-time 5 https://ip.me 2> /dev/null || \
2899+ curl -4 -sf --connect-timeout 4 --max-time 5 https://ip.wiredalter.com 2> /dev/null || \
2900+ curl -4 -sf --connect-timeout 4 --max-time 5 https://ifconfig.me 2> /dev/null || \
2901+ curl -4 -sf --connect-timeout 4 --max-time 5 https://icanhazip.com 2> /dev/null || \
28982902 echo " Unknown" )
28992903
2900- SERVER_IP_V6=$( curl -6 -s --connect-timeout 4 --max-time 5 https://ifconfig.me 2> /dev/null || \
2901- curl -6 -s --connect-timeout 4 --max-time 5 https://ip.me 2> /dev/null || \
2902- curl -6 -s --connect-timeout 4 --max-time 5 https://icanhazip.com 2> /dev/null || \
2904+ SERVER_IP_V6=$( curl -6 -sf --connect-timeout 4 --max-time 5 https://ip.me 2> /dev/null || \
2905+ curl -6 -sf --connect-timeout 4 --max-time 5 https://ip.wiredalter.com 2> /dev/null || \
2906+ curl -6 -sf --connect-timeout 4 --max-time 5 https://ifconfig.me 2> /dev/null || \
2907+ curl -6 -sf --connect-timeout 4 --max-time 5 https://icanhazip.com 2> /dev/null || \
29032908 echo " Not available" )
29042909
29052910 # --- Display Summary ---
@@ -3314,6 +3319,11 @@ show_connection_options() {
33143319 TS_IP=$( tailscale ip -4 2> /dev/null)
33153320 fi
33163321
3322+ local NB_IP=" "
3323+ if command -v netbird > /dev/null 2>&1 && netbird status 2> /dev/null | grep -q " Connected" ; then
3324+ NB_IP=$( ip -4 addr show wt0 2> /dev/null | awk ' /inet / {print $2}' | cut -d/ -f1 | head -1)
3325+ fi
3326+
33173327 printf " \n"
33183328
33193329 # 1. Public IP (Internet)
@@ -3349,6 +3359,12 @@ show_connection_options() {
33493359 if [[ -n " $TS_IP " ]]; then
33503360 printf " %-20s ${CYAN} ssh -p %s %s@%s${NC} \n" " Tailscale (VPN):" " $port " " $USERNAME " " $TS_IP "
33513361 fi
3362+
3363+ # 5. NetBird IP (VPN)
3364+ if [[ -n " $NB_IP " ]]; then
3365+ printf " %-20s ${CYAN} ssh -p %s %s@%s${NC} \n" " NetBird (VPN):" " $port " " $USERNAME " " $NB_IP "
3366+ fi
3367+
33523368 printf " \n"
33533369}
33543370
@@ -4642,7 +4658,7 @@ install_tailscale() {
46424658
46434659 print_info " Configuring Tailscale connection..."
46444660 printf ' %s\n' " ${CYAN} Choose Tailscale connection method:${NC} "
4645- printf ' 1) Standard Tailscale (requires pre-auth key from https://login.tailscale.com/admin)\n'
4661+ printf ' 1) Standard Tailscale (requires pre-auth key from https://login.tailscale.com/admin )\n'
46464662 printf ' 2) Custom Tailscale server (requires server URL and pre-auth key)\n'
46474663 read -rp " $( printf ' %s' " ${CYAN} Enter choice (1-2) [1]: ${NC} " ) " TS_CONNECTION
46484664 TS_CONNECTION=${TS_CONNECTION:- 1}
@@ -4792,6 +4808,111 @@ install_tailscale() {
47924808 log " Tailscale setup completed."
47934809}
47944810
4811+ install_netbird () {
4812+ if ! confirm " Install and configure NetBird VPN (Optional)?" ; then
4813+ print_info " Skipping NetBird installation."
4814+ log " NetBird installation skipped by user."
4815+ return 0
4816+ fi
4817+ print_section " NetBird VPN Installation and Configuration"
4818+
4819+ # Check if NetBird is already installed
4820+ if command -v netbird > /dev/null 2>&1 ; then
4821+ if systemctl is-active --quiet netbird && netbird status 2> /dev/null | grep -q " Connected" ; then
4822+ local NB_IPV4
4823+ NB_IPV4=$( ip -4 addr show wt0 2> /dev/null | awk ' /inet / {print $2}' | cut -d/ -f1 | head -1 || echo " Unknown" )
4824+ print_success " NetBird is active and connected. Node IPv4: $NB_IPV4 "
4825+ echo " $NB_IPV4 " > /tmp/netbird_ips.txt
4826+ return 0
4827+ else
4828+ print_warning " NetBird is installed but not active or connected."
4829+ fi
4830+ else
4831+ print_info " Adding NetBird repository and installing package..."
4832+ if ! apt-get update -qq || ! apt-get install -y -qq ca-certificates curl gnupg; then
4833+ print_error " Failed to install dependencies for NetBird."
4834+ return 1
4835+ fi
4836+
4837+ curl -sSL https://pkgs.netbird.io/debian/public.key | gpg --dearmor --output /usr/share/keyrings/netbird-archive-keyring.gpg 2> /dev/null
4838+ echo ' deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' | tee /etc/apt/sources.list.d/netbird.list > /dev/null
4839+
4840+ if ! apt-get update -qq || ! apt-get install -y -qq netbird; then
4841+ print_error " Failed to install NetBird package."
4842+ log " NetBird installation failed."
4843+ return 1
4844+ fi
4845+ print_success " NetBird installation complete."
4846+ log " NetBird installation completed."
4847+ fi
4848+
4849+ if ! confirm " Configure NetBird now?" ; then
4850+ print_info " You can configure NetBird later by running: sudo netbird up"
4851+ return 0
4852+ fi
4853+
4854+ print_info " Configuring NetBird connection..."
4855+ printf ' %s\n' " ${CYAN} Choose NetBird connection method:${NC} "
4856+ printf ' 1) Standard NetBird Cloud (requires setup key from https://app.netbird.io/setup-keys )\n'
4857+ printf ' 2) Custom/Self-hosted NetBird server (requires server URL and setup key)\n'
4858+
4859+ local NB_CONNECTION
4860+ read -rp " $( printf ' %s' " ${CYAN} Enter choice (1-2) [1]: ${NC} " ) " NB_CONNECTION
4861+ NB_CONNECTION=${NB_CONNECTION:- 1}
4862+
4863+ local SETUP_KEY MANAGEMENT_URL=" "
4864+ if [[ " $NB_CONNECTION " == " 2" ]]; then
4865+ while true ; do
4866+ read -rp " $( printf ' %s' " ${CYAN} Enter NetBird management URL (e.g., https://netbird.mydomain.com:33073): ${NC} " ) " MANAGEMENT_URL
4867+ if [[ " $MANAGEMENT_URL " =~ ^https? ://[a-zA-Z0-9.-]+ (:[0-9]+)? $ ]]; then break ; else print_error " Invalid URL. Try again." ; fi
4868+ done
4869+ fi
4870+
4871+ while true ; do
4872+ read -rsp " $( printf ' %s' " ${CYAN} Enter NetBird setup key: ${NC} " ) " SETUP_KEY
4873+ printf ' \n'
4874+ if [[ -n " $SETUP_KEY " ]]; then break ; else print_error " Setup key cannot be empty." ; fi
4875+ done
4876+
4877+ local NB_COMMAND=" netbird up --setup-key $SETUP_KEY "
4878+ if [[ " $NB_CONNECTION " == " 2" ]]; then
4879+ NB_COMMAND=" $NB_COMMAND --management-url $MANAGEMENT_URL "
4880+ fi
4881+
4882+ local NB_COMMAND_SAFE
4883+ NB_COMMAND_SAFE=$( echo " $NB_COMMAND " | sed -E ' s/--setup-key [^[:space:]]+/--setup-key REDACTED/g' )
4884+ print_info " Connecting to NetBird with: $NB_COMMAND_SAFE "
4885+
4886+ if ! $NB_COMMAND ; then
4887+ print_warning " Failed to connect to NetBird. Check setup key or network."
4888+ log " NetBird connection failed: $NB_COMMAND_SAFE "
4889+ else
4890+ # Verify connection
4891+ local RETRIES=3 DELAY=5 CONNECTED=false NB_IPV4=" "
4892+ for (( i= 1 ; i<= RETRIES; i++ )) ; do
4893+ if netbird status 2> /dev/null | grep -q " Connected" ; then
4894+ NB_IPV4=$( ip -4 addr show wt0 2> /dev/null | awk ' /inet / {print $2}' | cut -d/ -f1 | head -1 || echo " Unknown" )
4895+ if [[ -n " $NB_IPV4 " && " $NB_IPV4 " != " Unknown" ]]; then
4896+ CONNECTED=true
4897+ break
4898+ fi
4899+ fi
4900+ print_info " Waiting for NetBird to connect ($i /$RETRIES )..."
4901+ sleep $DELAY
4902+ done
4903+
4904+ if $CONNECTED ; then
4905+ print_success " NetBird connected successfully. Node IPv4 in VPN: $NB_IPV4 "
4906+ log " NetBird connected: $NB_COMMAND_SAFE "
4907+ echo " ${MANAGEMENT_URL:- https:// api.netbird.io} " > /tmp/netbird_server
4908+ echo " $NB_IPV4 " > /tmp/netbird_ips.txt
4909+ else
4910+ print_warning " NetBird connection attempt finished, but could not verify IP."
4911+ log " NetBird connection not verified: $NB_COMMAND_SAFE "
4912+ fi
4913+ fi
4914+ }
4915+
47954916setup_backup () {
47964917 print_section " Backup Configuration (rsync over SSH)"
47974918
@@ -5656,6 +5777,13 @@ generate_summary() {
56565777 fi
56575778 fi
56585779 fi
5780+ if command -v netbird > /dev/null 2>&1 ; then
5781+ if systemctl is-active --quiet netbird && netbird status 2> /dev/null | grep -q " Connected" ; then
5782+ printf " %-20s ${GREEN} ✓ Active & Connected${NC} \n" " netbird"
5783+ else
5784+ printf " %-20s ${YELLOW} ⚠ Installed but not connected${NC} \n" " netbird"
5785+ fi
5786+ fi
56595787 if [[ " ${AUDIT_RAN:- false} " == true ]]; then
56605788 printf " %-20s ${GREEN} ✓ Performed${NC} \n" " Security Audit"
56615789 else
@@ -5743,6 +5871,26 @@ generate_summary() {
57435871 printf ' %s\n' " Tailscale: ${RED} Not installed${NC} "
57445872 fi
57455873
5874+ # --- NetBird Summary ---
5875+ if command -v netbird > /dev/null 2>&1 ; then
5876+ local NB_CONFIGURED=false
5877+ if [[ -f /tmp/netbird_ips.txt ]] && grep -qE ' ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' /tmp/netbird_ips.txt 2> /dev/null; then
5878+ NB_CONFIGURED=true
5879+ fi
5880+ if $NB_CONFIGURED ; then
5881+ local NB_SERVER NB_IPS
5882+ NB_SERVER=$( cat /tmp/netbird_server 2> /dev/null || echo " https://api.netbird.io" )
5883+ NB_IPS=$( cat /tmp/netbird_ips.txt 2> /dev/null || echo " Not connected" )
5884+ printf ' %s\n' " NetBird: ${GREEN} Configured and connected${NC} "
5885+ printf " %-17s%s\n" " - Server:" " ${NB_SERVER} "
5886+ printf " %-17s%s\n" " - NetBird IPv4:" " ${NB_IPS} "
5887+ else
5888+ printf ' %s\n' " NetBird: ${YELLOW} Installed but not configured${NC} "
5889+ fi
5890+ else
5891+ printf ' %s\n' " NetBird: ${RED} Not installed${NC} "
5892+ fi
5893+
57465894 # --- Security Audit Summary ---
57475895 if [[ " ${AUDIT_RAN:- false} " == true ]]; then
57485896 printf ' %s\n' " Security Audit: ${GREEN} Performed${NC} "
@@ -5819,6 +5967,15 @@ generate_summary() {
58195967 fi
58205968 fi
58215969
5970+ # 3.1. NetBird Access
5971+ if [[ -f /tmp/netbird_ips.txt ]]; then
5972+ local NB_SUMMARY_IP
5973+ NB_SUMMARY_IP=$( head -n 1 /tmp/netbird_ips.txt)
5974+ if [[ -n " $NB_SUMMARY_IP " ]]; then
5975+ printf " %-26s ${CYAN} %s${NC} \n" " - NetBird (VPN):" " ssh -p $SSH_PORT $USERNAME @$NB_SUMMARY_IP "
5976+ fi
5977+ fi
5978+
58225979 # 4. IPv6 Access
58235980 if [[ " ${SERVER_IP_V6:- } " != " not available" && " ${SERVER_IP_V6:- } " != " Not available" ]]; then
58245981 printf " %-26s ${CYAN} %s${NC} \n" " - IPv6:" " ssh -p $SSH_PORT $USERNAME @$SERVER_IP_V6 "
@@ -5842,6 +5999,9 @@ generate_summary() {
58425999 if command -v tailscale > /dev/null 2>&1 ; then
58436000 printf " %-28s ${CYAN} %s${NC} \n" " - Tailscale status:" " tailscale status"
58446001 fi
6002+ if command -v netbird > /dev/null 2>&1 ; then
6003+ printf " %-28s ${CYAN} %s${NC} \n" " - NetBird status:" " netbird status"
6004+ fi
58456005 if [[ -f /root/run_backup.sh ]]; then
58466006 printf ' Remote Backup:\n'
58476007 printf " %-23s ${CYAN} %s${NC} \n" " - Test backup:" " sudo /root/run_backup.sh"
@@ -5971,6 +6131,7 @@ main() {
59716131 configure_kernel_hardening
59726132 install_docker
59736133 install_tailscale
6134+ install_netbird
59746135 setup_backup
59756136 configure_swap
59766137 configure_security_audit
0 commit comments