Files
big-bear-scripts-bigbeartec…/setup-pterodactyl-wings/run.sh
Christopher 5076849548 feat(setup-pterodactyl-wings): Use bash shebang for (#30)
portability

Changes the shebang line from `#!/bin/bash` to `#!/usr/bin/env bash` to
ensure the script runs on systems where the bash binary may not be
located at `/bin/bash`.
2025-01-09 14:55:10 -06:00

303 lines
11 KiB
Bash

#!/usr/bin/env bash
# Crafted by BigBearTechWorld
# Script to set up Pterodactyl server environment and networking
# Takes a UUID as an argument for server identification
CHECK_MARK="\033[0;32m✓\033[0m"
CROSS_MARK="\033[0;31m✗\033[0m"
WARNING_MARK="\033[0;33m⚠\033[0m"
# Function to print colored output
print_color() {
local color=$1
local message=$2
echo -e "\033[${color}m${message}\033[0m"
}
# Function to print section headers
print_header() {
local header=$1
echo "-------------------"
print_color "1;34" "$header"
echo "-------------------"
}
# Function to show spinner for long-running operations
show_spinner() {
local pid=$1
local delay=0.1
local spinstr='|/-\'
while ps -p $pid > /dev/null; do
for i in "${spinstr[@]}"; do
printf "\r[%c] " "$i"
sleep $delay
printf "\b\b\b\b"
done
done
printf "\r \b\b\b"
}
# Input validation: Check if exactly one argument (UUID) is provided
if [ $# -ne 1 ]; then
echo -e "${CROSS_MARK} Error: UUID argument is required"
echo "Usage: $0 <uuid>"
exit 1
fi
# Store the server UUID in a variable
UUID=$1
# Display Welcome ---->
print_header "BigBearCasaOS Setup Pterodactyl Wings V0.1"
echo "Here are some links:"
echo "https://community.bigbeartechworld.com"
echo "https://github.com/BigBearTechWorld"
echo ""
echo "If you would like to support me, please consider buying me a tea:"
echo "https://ko-fi.com/bigbeartechworld"
echo ""
# Display interactive menu for setup options ---->
echo "Please select an option:"
echo "1) Full setup - Create directories, set permissions, and configure networking"
echo "2) Quick setup - Only create directories and set permissions"
read -p "Enter your choice (1 or 2): " choice
# Process user's menu choice ---->
# Mission is a go 3 2 1 lift off!!!!
case $choice in
1)
echo -e "${CHECK_MARK} Selected: Full setup"
;;
2)
echo -e "${CHECK_MARK} Selected: Quick setup"
echo -e "\n${WARNING_MARK} Creating required directories..."
mkdir -p "/var/lib/pterodactyl/volumes"
mkdir -p "/tmp/pterodactyl"
mkdir -p "/etc/pterodactyl"
mkdir -p "/var/log/pterodactyl"
echo -e "${WARNING_MARK} Running chown commands..."
(chown -R 988:988 /tmp/pterodactyl /etc/pterodactyl /var/log/pterodactyl /var/lib/pterodactyl) &
show_spinner $!
echo -e "${CHECK_MARK} Chown commands completed successfully."
exit 0
;;
*)
echo -e "${CROSS_MARK} Invalid choice. Please run the script again and select 1 or 2."
exit 1
;;
esac
# Running full setup ---->
echo -e "\n${WARNING_MARK} Initiating full setup process..."
# Function to check if a subnet is already in use by either system routes or Docker networks
# Parameters:
# $1 = subnet to check (e.g., "172.40.0.0/16")
# $2 = network name to exclude from check (to avoid false positives with our own network)
# Returns:
# 0 (true) if subnet is in use
# 1 (false) if subnet is available
is_subnet_in_use() {
# Store input parameters in local variables for safety
local subnet=$1
local network_name=$2
# Remove CIDR notation (e.g., "172.40.0.0/16" becomes "172.40.0.0")
local subnet_base=${subnet%/*}
# First Phase: Check system routes for subnet usage
# Use process substitution to read output of 'ip route show'
while read -r route; do
# Check if the current route contains our subnet base
if [[ "$route" == *"$subnet_base"* ]]; then
# Extract interface name from route (e.g., "dev eth0" becomes "eth0")
# grep -o extracts only matching pattern
# cut -d' ' -f2 splits on space and takes second field
local iface=$(echo "$route" | grep -o "dev [^ ]*" | cut -d' ' -f2)
# Verify interface exists (-n) and isn't our pterodactyl interface
# We ignore pterodactyl0 to avoid detecting our own network
if [ -n "$iface" ] && [ "$iface" != "pterodactyl0" ]; then
return 0 # Subnet is in use by another interface
fi
fi
done < <(ip route show) # Feed output of 'ip route show' into while loop
# Second Phase: Check Docker networks for subnet usage
# Use process substitution to read Docker networks
while read -r other_network; do
# Skip checking our own network to avoid false positives
if [ "$other_network" != "$network_name" ]; then
# Inspect Docker network configuration
# 2>/dev/null suppresses errors if network doesn't exist
# grep -q quietly checks for subnet match
if docker network inspect "$other_network" 2>/dev/null | grep -q "\"Subnet\": \"$subnet\""; then
return 0 # Subnet is in use by another Docker network
fi
fi
done < <(docker network ls --format "{{.Name}}") # Get list of Docker networks
# If we get here, no conflicts were found
return 1 # Subnet is available for use
}
# Function to find an available subnet for Docker networking
# Takes a network name as parameter to avoid checking against itself
# Returns:
# - First available subnet via echo
# - 0 if subnet found successfully
# - 1 if no subnets are available
find_available_subnet() {
# Store network name parameter locally
local network_name=$1
# Define array of possible subnet ranges to try
# Using 172.40-47 range to avoid conflicts with:
# - Docker default subnet (172.17.0.0/16)
# - Common private network ranges
# Each subnet provides 65,534 usable addresses (/16 network)
local subnet_ranges=(
"172.40.0.0/16" # Provides 172.40.0.1 - 172.40.255.254
"172.41.0.0/16" # Provides 172.41.0.1 - 172.41.255.254
"172.42.0.0/16" # Provides 172.42.0.1 - 172.42.255.254
"172.43.0.0/16" # Provides 172.43.0.1 - 172.43.255.254
"172.44.0.0/16" # Provides 172.44.0.1 - 172.44.255.254
"172.45.0.0/16" # Provides 172.45.0.1 - 172.45.255.254
"172.46.0.0/16" # Provides 172.46.0.1 - 172.46.255.254
"172.47.0.0/16" # Provides 172.47.0.1 - 172.47.255.254
)
# Iterate through each subnet in the array until we find an available one
# [@] expands array to list of elements
for subnet in "${subnet_ranges[@]}"; do
# Check if subnet is available using is_subnet_in_use function
# ! inverts the return value since is_subnet_in_use returns 0 when in use
if ! is_subnet_in_use "$subnet" "$network_name"; then
echo "$subnet" # Output the available subnet
return 0 # Return success
fi
done
# If we've tried all subnets and found none available
return 1 # Return failure - no available subnets found :( <Mission Failed>
}
# Function to calculate gateway IP for a given subnet
get_gateway_for_subnet() {
local subnet=$1
echo "${subnet%.*}.1" # Use .1 as the gateway address
}
# Function to create Docker network for Pterodactyl
create_pterodactyl_network() {
local subnet=$1
local gateway=$2
# Remove existing network if it exists
if docker network ls | grep -q "pterodactyl_nw"; then
docker network rm pterodactyl_nw || true
fi
# Create new network with specified configuration
if ! docker network create \
--driver=bridge \
--ipv6 \
--subnet="$subnet" \
--gateway="$gateway" \
--subnet=fd00::/64 \
--gateway=fd00::1 \
-o "com.docker.network.bridge.name=pterodactyl0" \
-o "com.docker.network.bridge.enable_ip_masquerade=true" \
-o "com.docker.network.bridge.enable_icc=true" \
pterodactyl_nw; then
return 1 # Network creation failed
fi
# Verify network creation
if ! docker network ls | grep -q "pterodactyl_nw"; then
return 1 # Network creation failed
fi
return 0 # Return success :)
}
# Main function to orchestrate the network configuration process
# This function coordinates:
# - Finding an available subnet
# - Calculating the gateway address
# - Creating the Docker network
# Returns:
# 0 on success
# 1 on any error
configure_network() {
# Step 1: Find an available subnet
# Store result in local variable for safety
local new_subnet
# Call find_available_subnet and capture its output
# pterodactyl_nw is passed to avoid self-reference in subnet checks
new_subnet=$(find_available_subnet "pterodactyl_nw")
# Check return status of find_available_subnet
# $? contains the return value of the last command
if [ $? -ne 0 ]; then
echo -e "${CROSS_MARK} ERROR: Could not find available subnet"
return 1
fi
echo -e "${CHECK_MARK} Found available subnet: $new_subnet"
# Step 2: Calculate the gateway IP address for our subnet
# Store in local variable for safety
local new_gateway
# Call get_gateway_for_subnet to calculate the gateway IP
# Typically this will be the .1 address in the subnet
new_gateway=$(get_gateway_for_subnet "$new_subnet")
# Verify gateway calculation was successful
if [ $? -ne 0 ]; then
echo "ERROR: Could not determine gateway"
return 1 # Exit function with error
fi
# Echo results
echo -e "${CHECK_MARK} Gateway IP set to: $new_gateway"
# Step 3: Create the Docker network
# Use ! to invert the return value since we want to check for failure
# Pass both subnet and gateway to create_pterodactyl_network
if ! create_pterodactyl_network "$new_subnet" "$new_gateway"; then
echo "ERROR: Failed to create network"
return 1 # Exit function with error
fi
echo -e "${CHECK_MARK} Docker network created successfully"
# If we get here, all steps completed successfully Woohooo!!!
echo "Network created successfully"
return 0 # Return success :)
}
# Create directory structure for Pterodactyl
echo -e "\n${WARNING_MARK} Creating required directories..."
mkdir -p "/var/lib/pterodactyl/volumes/$UUID"
mkdir -p "/tmp/pterodactyl/$UUID"
mkdir -p "/etc/pterodactyl"
mkdir -p "/var/log/pterodactyl"
# Set up network configuration for Pterodactyl
echo -e "\n${WARNING_MARK} Configuring network..."
if ! configure_network; then
echo "ERROR: Network configuration failed"
exit 1
fi
# Set appropriate ownership for Pterodactyl directories
echo -e "\n${WARNING_MARK} Setting directory permissions..."
chown -R 988:988 /tmp/pterodactyl /etc/pterodactyl /var/log/pterodactyl /var/lib/pterodactyl
# Restart the Pterodactyl wings service
echo -e "\n${WARNING_MARK} Restarting pterodactyl-wings container..."
if ! docker restart pterodactyl-wings; then
echo -e "${CROSS_MARK} WARNING: Failed to restart pterodactyl-wings container"
echo "Please restart it manually using: docker restart pterodactyl-wings"
fi
# Mission is complete
echo -e "\n${CHECK_MARK} Setup completed successfully for UUID: $UUID"