Skip to content

Instantly share code, notes, and snippets.

@menggatot
Last active January 27, 2025 15:13
Show Gist options
  • Save menggatot/c5ccb55b841261887496b1096d7992d7 to your computer and use it in GitHub Desktop.
Save menggatot/c5ccb55b841261887496b1096d7992d7 to your computer and use it in GitHub Desktop.
Tailscale IP Forward Script

🌐 Tailscale IP Forward Script

A sophisticated CLI tool for managing IP forwarding between local interfaces and Tailscale devices with advanced port preservation capabilities 🚀

License: MIT

📝 Description

This script provides a robust, interactive interface for configuring IP forwarding and NAT rules between your local network interfaces and Tailscale devices. It features a colorful CLI interface, comprehensive error handling, automatic rule persistence, and flexible port preservation options for both TCP and UDP protocols. The script ensures smooth integration between your local network and Tailscale mesh network while maintaining critical service accessibility.

✨ Features

  • 🎨 Beautiful colorful CLI interface with intuitive prompts
  • 📋 Interactive menu system with smart defaults for rapid deployment
  • 🔒 Configurable TCP and UDP port preservation with custom port support
  • 🔄 Automatic rule persistence with multiple fallback methods
  • 💾 Comprehensive backup system with timestamped configurations
  • 🛡️ Advanced error handling with automatic rollback capability
  • 🔍 Dynamic interface and Tailscale device detection
  • ⚡ Smart defaults for streamlined configuration
  • 🌐 Complete NAT and forwarding rule management
  • 🔌 Multi-protocol support (TCP/UDP/ICMP)
  • 🔄 Connection state tracking for established sessions
  • 📊 Real-time configuration display and verification

📋 Prerequisites

  • Root privileges
  • Tailscale installed and configured
  • iptables package
  • netfilter-persistent (recommended)
  • Basic understanding of networking concepts

📦 Installation

Dependencies Installation

Debian/Ubuntu

# Install required packages
sudo apt update
sudo apt install -y iptables iptables-persistent netfilter-persistent

RHEL/CentOS

# Install required packages
sudo yum install -y iptables-services
sudo systemctl enable iptables
sudo systemctl start iptables

Arch Linux

# Install required packages
sudo pacman -Syu
sudo pacman -S iptables iptables-nft

Tailscale Installation

# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh

Script Installation

  1. Download the script:
curl -O <script_url>
  1. Make it executable:
chmod +x tailscale-forward.sh

🚀 Usage

  1. Execute with root privileges:
sudo ./tailscale-forward.sh
  1. Follow the interactive configuration process:
    • Select source interface/IP (Enter for default)
    • Choose target Tailscale device (Enter for custom IP)
    • Configure port preservation settings (Enter for defaults)
    • Review and confirm the configuration (Enter for Yes)

🔐 Port Configuration

Default Port Preservation

  • Standard Mode:
    • TCP: Preserves SSH access (port 22)
    • UDP: Preserves Tailscale connectivity (port 41641)
  • Custom Mode:
    • Specify multiple ports to preserve
    • Format: Space-separated list of port numbers
    • Examples:
      • TCP: 22 80 443 (preserves SSH, HTTP, HTTPS)
      • UDP: 41641 53 (preserves Tailscale, DNS)
    • All non-preserved ports are forwarded to the Tailscale device
  1. Edit Tailscale configuration:
sudo nano /etc/default/tailscaled
  1. Modify port setting:
PORT="41641"    # Replace with desired port
  1. Apply changes:
sudo systemctl restart tailscaled

💾 Configuration Persistence

The script implements a tiered approach to rule persistence:

  1. Primary: netfilter-persistent service

    netfilter-persistent save
  2. Secondary: iptables-save direct to rules file

    iptables-save > /etc/iptables/rules.v4
  3. Backup: Timestamped configurations

    /etc/iptables/backup/rules.v4.YYYYMMDD_HHMMSS

⚙️ Advanced Configuration

LXC Container Requirements

Add to /etc/pve/lxc/<CT_ID>.conf on Proxmox host:

lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file

🔍 Troubleshooting

Common Issues and Solutions

  1. Permission Errors

    [ERROR] Please run as root
    

    Solution: Execute with sudo privileges

  2. Tailscale Service Issues

    [ERROR] Could not get Tailscale devices
    

    Solution: Verify Tailscale service status

    sudo systemctl status tailscaled
    sudo tailscale status
  3. Interface Detection Problems

    Error: Failed to get network interface information
    

    Solution: Check network interface configuration

    ip addr show
  4. Rule Persistence Failures

    [ERROR] Failed to save rules
    

    Solution: Verify storage locations

    ls -la /etc/iptables/
    systemctl status netfilter-persistent
  5. Port Conflicts

    [ERROR] Failed to apply iptables rules
    

    Solution: Check port availability

    sudo lsof -i :<port_number>

🛡️ License

MIT License - See LICENSE file for details

🤝 Contributing

Contributions are welcome! Please feel free to submit pull requests or create issues for bugs and feature requests.

🙏 Acknowledgments

  • Tailscale team for their innovative VPN solution
  • Proxmox community for LXC container expertise
  • iptables community for robust networking tools
  • All contributors and users who provide valuable feedback
#!/bin/bash
# Colors and formatting
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# Default essential ports
DEFAULT_TCP_PORT=22
DEFAULT_UDP_PORT=41641
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}${BOLD}[ERROR]${NC} Please run as root"
exit 1
fi
# Check for iptables-persistent installation
check_iptables_persistent() {
if ! dpkg -l | grep -q "iptables-persistent"; then
echo -e "${YELLOW}[INFO]${NC} iptables-persistent is not installed"
read -p $'\033[0;36mWould you like to install iptables-persistent? [Y/n]: \033[0m' install_choice
install_choice=${install_choice:-Y}
if [[ $install_choice =~ ^[Yy]$ ]]; then
# Pre-seed the debconf database to avoid interactive prompt
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections
apt-get update
if apt-get install -y iptables-persistent; then
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} iptables-persistent installed successfully"
else
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to install iptables-persistent"
return 1
fi
else
echo -e "${YELLOW}[WARNING]${NC} Continuing without iptables-persistent"
return 1
fi
fi
return 0
}
# Enhanced save function with iptables-persistent support
save_iptables() {
echo -e "${YELLOW}[INFO]${NC} Saving iptables rules..."
# Create backup directory if it doesn't exist
mkdir -p /etc/iptables/backup
# Create timestamped backup
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="/etc/iptables/backup/rules.v4.${timestamp}"
# Backup current rules
if ! iptables-save > "$backup_file"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup"
return 1
fi
# Try saving with netfilter-persistent first
if command -v netfilter-persistent &>/dev/null; then
if netfilter-persistent save; then
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved using netfilter-persistent"
echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}"
return 0
fi
fi
# Fallback to manual save
if [ -d "/etc/iptables" ]; then
if iptables-save > /etc/iptables/rules.v4; then
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved to /etc/iptables/rules.v4"
echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}"
return 0
fi
fi
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save iptables rules"
return 1
}
# Enhanced restore function with iptables-persistent support
restore_iptables() {
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous rules..."
# Try restoring from backup first
local latest_backup=$(ls -t /etc/iptables/backup/rules.v4.* 2>/dev/null | head -n1)
if [ -n "$latest_backup" ]; then
echo -e "${YELLOW}[INFO]${NC} Restoring from latest backup: ${latest_backup}"
if iptables-restore < "$latest_backup"; then
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from backup"
# Save the restored rules
save_iptables
return 0
fi
fi
# Fallback to default rules file
if [ -f /etc/iptables/rules.v4 ]; then
echo -e "${YELLOW}[INFO]${NC} Attempting to restore from /etc/iptables/rules.v4"
if iptables-restore < /etc/iptables/rules.v4; then
echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from default file"
return 0
fi
fi
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore iptables rules"
return 1
}
# Add this near the beginning of the script execution
check_iptables_persistent
# Function to print section header
print_header() {
echo -e "\n${BLUE}${BOLD}$1${NC}"
echo -e "${BLUE}${BOLD}$(printf '=%.0s' {1..50})${NC}\n"
}
# Function to display current NAT configuration
display_current_config() {
print_header "Current NAT Configuration"
echo -e "${CYAN}PREROUTING Rules (DNAT):${NC}"
if ! iptables -t nat -L PREROUTING -n -v | grep -q "DNAT"; then
echo -e "${YELLOW}No DNAT rules configured${NC}"
else
iptables -t nat -L PREROUTING -n -v | grep "DNAT"
fi
echo -e "\n${CYAN}POSTROUTING Rules (SNAT/MASQUERADE):${NC}"
if ! iptables -t nat -L POSTROUTING -n -v | grep -q "SNAT\|MASQUERADE"; then
echo -e "${YELLOW}No SNAT/MASQUERADE rules configured${NC}"
else
iptables -t nat -L POSTROUTING -n -v | grep "SNAT\|MASQUERADE"
fi
echo -e "\n${CYAN}Forward Rules:${NC}"
if ! iptables -L FORWARD -n -v | grep -q "ACCEPT\|DROP"; then
echo -e "${YELLOW}No specific forwarding rules configured${NC}"
else
iptables -L FORWARD -n -v | grep "ACCEPT\|DROP"
fi
echo # Empty line for spacing
}
# Function to get IP and interface pairs (excluding localhost)
get_interfaces() {
ip -4 addr | awk '/inet / && !/127.0.0.1/ {print $2 "\t" $NF}' | sed 's/\/[0-9]*//g'
}
# Get interface name from selected IP
get_interface_name() {
local ip=$1
ip -4 addr | grep "$ip" | awk '{print $NF}'
}
# Function to get Tailscale IPs
get_tailscale_devices() {
tailscale status | grep -v '^#' | awk 'NF>1 {print $2 " - " $1}'
}
# Function to get interface details for Tailscale device
get_tailscale_device_name() {
local ip=$1
tailscale status | grep "$ip" | awk '{print $2}'
}
# Function to get subnet from IP
get_subnet() {
local ip=$1
ip -4 addr | grep "$ip" | awk '{print $2}'
}
# Display current configuration at startup
display_current_config
# Step 1: Get source IP
print_header "Step 1: Select source IP address"
echo -e "${CYAN}Available interfaces:${NC}"
echo -e "${YELLOW}0)${NC} Enter custom IP"
# Display interface options
declare -a interfaces
while IFS=$'\t' read -r ip interface; do
interfaces+=("$ip - $interface")
done < <(get_interfaces)
for i in "${!interfaces[@]}"; do
echo -e "${YELLOW}$((i + 1)))${NC} ${interfaces[$i]}"
done
echo -e "${YELLOW}C)${NC} Cancel"
echo -e "${CYAN}Press Enter to select option 1${NC}"
read -p $'\033[0;36mSelect an option: \033[0m' choice
# Default to option 1 if Enter is pressed
choice=${choice:-1}
case $choice in
0)
read -p $'\033[0;36mEnter custom IP: \033[0m' source_ip
source_subnet=$(get_subnet "$source_ip")
;;
[1-9]*)
if [ "$choice" -le "${#interfaces[@]}" ]; then
source_ip=$(echo "${interfaces[$((choice - 1))]}" | cut -d' ' -f1)
source_subnet=$(get_subnet "$source_ip")
else
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
exit 1
fi
;;
[Cc])
echo -e "${YELLOW}Operation cancelled${NC}"
exit 0
;;
*)
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
exit 1
;;
esac
# Step 2: Get target IP
print_header "Step 2: Select target Tailscale IP address"
echo -e "${CYAN}Available Tailscale devices:${NC}"
echo -e "${YELLOW}0)${NC} Enter custom IP"
# Display Tailscale devices
declare -a tailscale_devices
mapfile -t tailscale_devices < <(get_tailscale_devices)
for i in "${!tailscale_devices[@]}"; do
echo -e "${YELLOW}$((i + 1)))${NC} ${tailscale_devices[$i]}"
done
echo -e "${YELLOW}C)${NC} Cancel"
echo -e "${CYAN}Press Enter to select option 0 (custom IP)${NC}"
read -p $'\033[0;36mSelect an option: \033[0m' choice
# Default to option 0 if Enter is pressed
choice=${choice:-0}
case $choice in
0)
read -p $'\033[0;36mEnter custom IP: \033[0m' target_ip
target_device="custom"
;;
[1-9]*)
if [ "$choice" -le "${#tailscale_devices[@]}" ]; then
selected_device="${tailscale_devices[$((choice-1))]}"
target_ip=$(echo "$selected_device" | cut -d' ' -f3)
target_device=$(echo "$selected_device" | cut -d' ' -f1)
else
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
exit 1
fi
;;
[Cc])
echo -e "${YELLOW}Operation cancelled${NC}"
exit 0
;;
*)
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
exit 1
;;
esac
# Step 3: Port Configuration
print_header "Step 3: Configure Port Settings"
echo -e "${CYAN}Port Configuration Options:${NC}"
echo -e "${YELLOW}1)${NC} Use default ports only (SSH TCP 22, Tailscale UDP 41641)"
echo -e "${YELLOW}2)${NC} Configure custom ports"
echo -e "${CYAN}Press Enter to use default ports only${NC}"
read -p $'\033[0;36mSelect an option: \033[0m' port_choice
port_choice=${port_choice:-1}
declare -a PRESERVED_TCP_PORTS
declare -a PRESERVED_UDP_PORTS
case $port_choice in
1)
PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT)
PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT)
echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}"
echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}"
;;
2)
echo -e "\n${CYAN}Enter TCP ports to preserve (space-separated, e.g., '22 80 443')${NC}"
echo -e "${CYAN}Press Enter to use default TCP port ${DEFAULT_TCP_PORT}${NC}"
read -p $'\033[0;36mTCP Ports: \033[0m' custom_tcp_ports
if [ -z "$custom_tcp_ports" ]; then
PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT)
echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}"
else
PRESERVED_TCP_PORTS=($custom_tcp_ports)
echo -e "${GREEN}Using custom TCP ports: ${custom_tcp_ports}${NC}"
fi
echo -e "\n${CYAN}Enter UDP ports to preserve (space-separated, e.g., '41641 53')${NC}"
echo -e "${CYAN}Press Enter to use default UDP port ${DEFAULT_UDP_PORT}${NC}"
read -p $'\033[0;36mUDP Ports: \033[0m' custom_udp_ports
if [ -z "$custom_udp_ports" ]; then
PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT)
echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}"
else
PRESERVED_UDP_PORTS=($custom_udp_ports)
echo -e "${GREEN}Using custom UDP ports: ${custom_udp_ports}${NC}"
fi
;;
*)
echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option"
exit 1
;;
esac
# Step 4: Confirmation
print_header "Step 4: Review your selections"
source_interface=$(get_interface_name "$source_ip")
echo -e "${CYAN}Source Details:${NC}"
echo -e " IP Address: ${BOLD}$source_ip${NC}"
echo -e " Interface: ${BOLD}$source_interface${NC}"
echo -e " Subnet: ${BOLD}$source_subnet${NC}"
echo -e "\n${CYAN}Target Details:${NC}"
echo -e " IP Address: ${BOLD}$target_ip${NC}"
echo -e " Device: ${BOLD}$target_device${NC}"
echo -e "\n${CYAN}Port Configuration:${NC}"
echo -e " Preserved TCP Ports: ${BOLD}${PRESERVED_TCP_PORTS[*]}${NC}"
echo -e " Preserved UDP Ports: ${BOLD}${PRESERVED_UDP_PORTS[*]}${NC}"
echo -e "\n${YELLOW}This will forward traffic from $source_interface to tailscale0${NC}"
read -p $'\033[1;33mApply these changes? [Y/N, default is Y]: \033[0m' confirm
# Default to 'Y' if no input is provided
confirm=${confirm:-Y}
if [[ $confirm =~ ^[Yy]$ ]]; then
# Step 5: Apply iptables configuration
print_header "Step 5: Applying configuration..."
# Create backup before any changes
echo -e "${YELLOW}[1/7]${NC} Creating backup..."
mkdir -p /etc/iptables/backup
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="/etc/iptables/backup/rules.v4.${timestamp}"
if ! iptables-save > "$backup_file"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup. Aborting configuration"
exit 1
fi
if [ ! -s "$backup_file" ]; then
echo -e "${RED}${BOLD}[ERROR]${NC} Backup file is empty. Aborting configuration"
exit 1
fi
# Check if IP forwarding is enabled
if [ "$(sysctl -n net.ipv4.ip_forward)" -ne 1 ]; then
echo -e "${YELLOW}[2/7]${NC} Enabling IP forwarding..."
if ! echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf || ! sysctl -w net.ipv4.ip_forward=1; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to enable IP forwarding"
exit 1
fi
# Also add to sysctl.d for persistent configuration
if ! echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-tailscale.conf; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create persistent IP forwarding configuration"
exit 1
fi
sysctl -p /etc/sysctl.d/99-tailscale.conf
else
echo -e "${YELLOW}[2/7]${NC} IP forwarding is already enabled"
fi
# Get interface name
source_interface=$(get_interface_name "$source_ip")
if [ -z "$source_interface" ]; then
echo -e "${RED}${BOLD}[ERROR]${NC} Could not determine interface name for IP $source_ip"
exit 1
fi
# Apply rules
echo -e "${YELLOW}[3/7]${NC} Applying new rules..."
# Clear existing NAT rules
echo -e "${CYAN}Clearing existing NAT and forwarding rules...${NC}"
iptables -t nat -F
iptables -F FORWARD
# Allow preserved TCP ports to reach the host
for port in "${PRESERVED_TCP_PORTS[@]}"; do
echo -e "${CYAN}Preserving TCP host access on port ${port}${NC}"
iptables -t nat -A PREROUTING -d "${source_ip}" -p tcp --dport ${port} -j ACCEPT
iptables -A FORWARD -p tcp --dport ${port} -j ACCEPT
done
# Allow preserved UDP ports to reach the host
for port in "${PRESERVED_UDP_PORTS[@]}"; do
echo -e "${CYAN}Preserving UDP host access on port ${port}${NC}"
iptables -t nat -A PREROUTING -d "${source_ip}" -p udp --dport ${port} -j ACCEPT
iptables -A FORWARD -p udp --dport ${port} -j ACCEPT
# Additional rule to ensure UDP responses can return
iptables -A FORWARD -p udp --sport ${port} -j ACCEPT
done
# Set up NAT rules for all other ports
echo -e "${CYAN}Setting up NAT rules...${NC}"
if ! iptables -t nat -A PREROUTING -d "${source_ip}" -j DNAT --to-destination "${target_ip}"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up DNAT rules"
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..."
if ! iptables-restore < "$backup_file"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup: $backup_file"
echo -e "${RED}${BOLD}[WARNING]${NC} Your firewall rules may be in an inconsistent state"
echo -e "${RED}${BOLD}[WARNING]${NC} Please check your configuration manually"
else
echo -e "${GREEN}[SUCCESS]${NC} Successfully restored previous configuration"
fi
exit 1
fi
if ! iptables -t nat -A POSTROUTING -s "${target_ip}" -j SNAT --to-source "${source_ip}"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up SNAT rules"
echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..."
if ! iptables-restore < "$backup_file"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup"
exit 1
fi
exit 1
fi
# Set up FORWARD chain
echo -e "${CYAN}Setting up forwarding rules...${NC}"
iptables -A FORWARD -i ${source_interface} -o tailscale0 -j ACCEPT
iptables -A FORWARD -i tailscale0 -o ${source_interface} -j ACCEPT
iptables -A FORWARD -p icmp -j ACCEPT
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
# Add Tailscale subnet handling
echo -e "${CYAN}Setting up Tailscale subnet rules...${NC}"
iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to-source "${source_ip}"
iptables -t nat -A POSTROUTING -s "${source_subnet}" -j MASQUERADE
# Verify configuration
echo -e "${YELLOW}[4/7]${NC} Verifying configuration..."
if ! iptables -t nat -L PREROUTING -n -v >/dev/null 2>&1; then
echo -e "${RED}${BOLD}[ERROR]${NC} Configuration verification failed. Restoring previous rules..."
if ! iptables-restore < "$backup_file"; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup"
exit 1
fi
exit 1
fi
# Enable service
echo -e "${YELLOW}[5/7]${NC} Enabling iptables-persistent service..."
if systemctl is-active --quiet netfilter-persistent; then
systemctl restart netfilter-persistent
else
systemctl enable netfilter-persistent
systemctl start netfilter-persistent
fi
# Save current configuration
echo -e "${YELLOW}[6/7]${NC} Saving current configuration..."
if ! save_iptables; then
echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save configuration. Current rules may be lost on reboot"
exit 1
fi
# Final status
echo -e "${YELLOW}[7/7]${NC} Configuration complete"
echo -e "\n${GREEN}${BOLD}[SUCCESS]${NC} Configuration completed successfully!"
echo -e "${CYAN}Preserved TCP ports: ${PRESERVED_TCP_PORTS[*]}${NC}"
echo -e "${CYAN}Preserved UDP ports: ${PRESERVED_UDP_PORTS[*]}${NC}"
echo -e "${CYAN}All other ports are forwarded to the Tailscale device${NC}"
echo -e "${CYAN}Backup saved to: $backup_file${NC}\n"
else
echo -e "\n${YELLOW}Operation cancelled by user${NC}"
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment