Clean up - simple zero-intervention bootstrap

- Removed ansible, compose, docs, scripts, tasks, templates
- Simplified bootstrap.sh for all major distros
- Works on Ubuntu, Debian, Fedora, Rocky, Arch, openSUSE
- Installs Docker, Tailscale, essential tools
- Configures firewall automatically

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Vish-hands
2026-01-10 09:04:07 +00:00
parent 24f2cd64e9
commit cddeee6849
69 changed files with 341 additions and 9743 deletions

View File

@@ -1,524 +1,324 @@
#!/bin/bash
# 🚀 All-in-One Bootstrap Script for *arr Media Stack
# This script sets up everything from a fresh OS install to a fully deployed media stack
#
# Usage: curl -sSL https://github.com/your-username/arr-suite-template/raw/branch/main/bootstrap.sh | bash
# Or: wget -qO- https://github.com/your-username/arr-suite-template/raw/branch/main/bootstrap.sh | bash
# =============================================================================
# Server Bootstrap Script
# =============================================================================
# Prepares a fresh server with Docker, Tailscale, and essential tools.
# Zero intervention required - just run and go.
#
# Tested on: Ubuntu 20.04+, Debian 11+
# Requirements: Fresh VPS with sudo access
# Supported: Ubuntu, Debian, Fedora, Rocky/Alma/RHEL, Arch, openSUSE
#
# Usage:
# curl -fsSL <url>/bootstrap.sh | sudo bash
#
# Options:
# --no-tailscale Skip Tailscale installation
# --no-firewall Skip firewall configuration
# =============================================================================
set -euo pipefail
# Configuration
REPO_URL="https://github.com/your-username/arr-suite-template.git"
INSTALL_DIR="/opt/arr-stack"
SERVICE_USER="arrstack"
PYTHON_VERSION="3.9"
# Colors for output
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
NC='\033[0m'
# Logging functions
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
log() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# Configuration
SKIP_TAILSCALE=false
SKIP_FIREWALL=false
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--no-tailscale) SKIP_TAILSCALE=true; shift ;;
--no-firewall) SKIP_FIREWALL=true; shift ;;
--help|-h)
echo "Usage: bootstrap.sh [--no-tailscale] [--no-firewall]"
exit 0
;;
*) shift ;;
esac
done
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Check root
[[ $EUID -ne 0 ]] && error "Run as root: sudo bash bootstrap.sh"
info() {
echo -e "${CYAN}[INFO]${NC} $1"
}
step() {
echo -e "${PURPLE}[STEP]${NC} $1"
}
# Check if running as root
check_root() {
if [[ $EUID -eq 0 ]]; then
error "This script should not be run as root. Please run as a regular user with sudo privileges."
exit 1
fi
# Check sudo access
if ! sudo -n true 2>/dev/null; then
error "This script requires sudo privileges. Please ensure your user can use sudo."
exit 1
fi
}
# Detect OS and version
# Detect OS
detect_os() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS=$ID
OS_VERSION=$VERSION_ID
OS_VERSION=${VERSION_ID:-}
OS_NAME=$NAME
else
error "Cannot detect OS. This script supports Ubuntu 20.04+ and Debian 11+"
exit 1
error "Cannot detect OS"
fi
log "Detected: $OS_NAME $OS_VERSION"
}
# Install essential packages
install_essentials() {
log "Installing essential packages..."
log "Detected OS: $OS $OS_VERSION"
# Verify supported OS
case $OS in
ubuntu)
if [[ $(echo "$OS_VERSION >= 20.04" | bc -l) -eq 0 ]]; then
error "Ubuntu 20.04 or higher required. Found: $OS_VERSION"
exit 1
fi
ubuntu|debian|linuxmint|pop)
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
curl wget git unzip htop nano vim \
ca-certificates gnupg lsb-release \
jq tree ncdu lsof net-tools
;;
debian)
if [[ $(echo "$OS_VERSION >= 11" | bc -l) -eq 0 ]]; then
error "Debian 11 or higher required. Found: $OS_VERSION"
exit 1
fi
fedora)
dnf install -y -q \
curl wget git unzip htop nano vim \
ca-certificates gnupg \
jq tree ncdu lsof net-tools
;;
rocky|almalinux|rhel|centos)
dnf install -y -q epel-release 2>/dev/null || true
dnf install -y -q \
curl wget git unzip htop nano vim \
ca-certificates gnupg \
jq tree ncdu lsof net-tools
;;
arch|manjaro|endeavouros)
pacman -Sy --noconfirm \
curl wget git unzip htop nano vim \
ca-certificates gnupg \
jq tree ncdu lsof net-tools
;;
opensuse*|sles)
zypper install -y \
curl wget git unzip htop nano vim \
ca-certificates \
jq tree ncdu lsof net-tools
;;
*)
error "Unsupported OS: $OS. This script supports Ubuntu 20.04+ and Debian 11+"
exit 1
warn "Unknown OS: $OS - skipping package installation"
;;
esac
success "OS compatibility verified"
}
# Check system requirements
check_requirements() {
step "Checking system requirements..."
# Check memory (minimum 4GB)
MEMORY_GB=$(free -g | awk '/^Mem:/{print $2}')
if [[ $MEMORY_GB -lt 4 ]]; then
error "Minimum 4GB RAM required. Found: ${MEMORY_GB}GB"
exit 1
fi
# Check disk space (minimum 50GB)
DISK_GB=$(df / | awk 'NR==2{print int($4/1024/1024)}')
if [[ $DISK_GB -lt 50 ]]; then
error "Minimum 50GB free disk space required. Found: ${DISK_GB}GB"
exit 1
fi
# Check architecture
ARCH=$(uname -m)
if [[ $ARCH != "x86_64" ]]; then
error "x86_64 architecture required. Found: $ARCH"
exit 1
fi
success "System requirements met: ${MEMORY_GB}GB RAM, ${DISK_GB}GB disk, $ARCH"
}
# Update system packages
update_system() {
step "Updating system packages..."
sudo apt-get update -qq
sudo apt-get upgrade -y -qq
sudo apt-get install -y -qq \
curl \
wget \
git \
unzip \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
bc \
jq \
htop \
nano \
vim \
ufw \
fail2ban
success "System packages updated"
}
# Install Python and pip
install_python() {
step "Installing Python $PYTHON_VERSION and pip..."
# Add deadsnakes PPA for newer Python versions on Ubuntu
if [[ $OS == "ubuntu" ]]; then
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt-get update -qq
fi
sudo apt-get install -y -qq \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-pip \
python${PYTHON_VERSION}-venv \
python${PYTHON_VERSION}-dev
# Create symlinks
sudo ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python3
sudo ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python
# Install pip if not available
if ! command -v pip3 &> /dev/null; then
curl -sSL https://bootstrap.pypa.io/get-pip.py | sudo python${PYTHON_VERSION}
fi
success "Python $PYTHON_VERSION installed"
success "Essential packages installed"
}
# Install Docker
install_docker() {
step "Installing Docker..."
# Remove old Docker versions
sudo apt-get remove -y -qq docker docker-engine docker.io containerd runc 2>/dev/null || true
# Add Docker GPG key
curl -fsSL https://download.docker.com/linux/$OS/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add Docker repository
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/$OS $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt-get update -qq
sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Start and enable Docker
sudo systemctl start docker
sudo systemctl enable docker
# Add user to docker group
sudo usermod -aG docker $USER
success "Docker installed and configured"
}
# Install Ansible
install_ansible() {
step "Installing Ansible..."
# Install Ansible via pip for latest version
python3 -m pip install --user ansible ansible-core
# Add to PATH
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
export PATH="$HOME/.local/bin:$PATH"
# Verify installation
if command -v ansible &> /dev/null; then
ANSIBLE_VERSION=$(ansible --version | head -n1 | cut -d' ' -f3)
success "Ansible $ANSIBLE_VERSION installed"
else
error "Ansible installation failed"
exit 1
if command -v docker &>/dev/null; then
success "Docker already installed"
return
fi
log "Installing Docker..."
case $OS in
ubuntu|debian|linuxmint|pop)
# Remove old versions
apt-get remove -y -qq docker docker-engine docker.io containerd runc 2>/dev/null || true
install -m 0755 -d /etc/apt/keyrings
# Determine base OS for Docker repo
DOCKER_OS=$OS
if [[ "$OS" == "linuxmint" || "$OS" == "pop" ]]; then
DOCKER_OS="ubuntu"
fi
curl -fsSL https://download.docker.com/linux/$DOCKER_OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null
chmod a+r /etc/apt/keyrings/docker.gpg
# Get codename
if [[ -n "${VERSION_CODENAME:-}" ]]; then
CODENAME=$VERSION_CODENAME
else
CODENAME=$(lsb_release -cs 2>/dev/null || echo "jammy")
fi
# Map derivative codenames to Ubuntu
case $OS in
linuxmint|pop)
case $OS_VERSION in
20*|21.0|21.1) CODENAME="focal" ;;
21.2|21.3|22*) CODENAME="jammy" ;;
*) CODENAME="jammy" ;;
esac
;;
esac
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$DOCKER_OS $CODENAME stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
fedora)
dnf remove -y -q docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 2>/dev/null || true
dnf install -y -q dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
rocky|almalinux|rhel|centos)
dnf remove -y -q docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 2>/dev/null || true
dnf install -y -q dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y -q docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
arch|manjaro|endeavouros)
pacman -Sy --noconfirm docker docker-compose
;;
opensuse*|sles)
zypper install -y docker docker-compose
;;
*)
error "Unsupported OS for Docker: $OS"
;;
esac
systemctl enable --now docker
success "Docker installed"
}
# Install Tailscale (optional but recommended)
# Install Tailscale
install_tailscale() {
step "Installing Tailscale..."
$SKIP_TAILSCALE && return
# Add Tailscale repository
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | sudo tee /etc/apt/sources.list.d/tailscale.list
# Install Tailscale
sudo apt-get update -qq
sudo apt-get install -y -qq tailscale
success "Tailscale installed (run 'sudo tailscale up' to connect)"
}
# Clone repository
clone_repository() {
step "Cloning arr-stack repository..."
# Create install directory
sudo mkdir -p $INSTALL_DIR
sudo chown $USER:$USER $INSTALL_DIR
# Clone repository
git clone $REPO_URL $INSTALL_DIR
cd $INSTALL_DIR
success "Repository cloned to $INSTALL_DIR"
}
# Setup configuration
setup_configuration() {
step "Setting up configuration files..."
cd $INSTALL_DIR
# Create inventory from example
if [[ ! -f inventory/production.yml ]]; then
cp inventory/production.yml.example inventory/production.yml
# Get server IP
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip || hostname -I | awk '{print $1}')
# Update inventory with current server details
sed -i "s/your_server_ip/$SERVER_IP/g" inventory/production.yml
sed -i "s/your_ssh_user/$USER/g" inventory/production.yml
info "Updated inventory with server IP: $SERVER_IP"
if command -v tailscale &>/dev/null; then
success "Tailscale already installed"
return
fi
# Create vault file from example
if [[ ! -f group_vars/all/vault.yml ]]; then
cp group_vars/all/vault.yml.example group_vars/all/vault.yml
info "Created vault.yml from example - you'll need to edit this with your credentials"
fi
success "Configuration files created"
log "Installing Tailscale..."
curl -fsSL https://tailscale.com/install.sh | sh
success "Tailscale installed - run 'sudo tailscale up' to connect"
}
# Configure firewall
configure_firewall() {
step "Configuring UFW firewall..."
$SKIP_FIREWALL && return
# Reset UFW to defaults
sudo ufw --force reset
log "Configuring firewall..."
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH
sudo ufw allow ssh
# Allow Plex (public access)
sudo ufw allow 32400/tcp
# Allow Tailscale
sudo ufw allow in on tailscale0
# Enable firewall
sudo ufw --force enable
success "UFW firewall configured"
case $OS in
ubuntu|debian|linuxmint|pop)
if ! command -v ufw &>/dev/null; then
apt-get install -y -qq ufw
fi
ufw --force reset >/dev/null 2>&1
ufw default deny incoming >/dev/null
ufw default allow outgoing >/dev/null
ufw allow ssh >/dev/null
ufw allow 32400/tcp >/dev/null # Plex
ufw allow 8096/tcp >/dev/null # Jellyfin
ufw --force enable >/dev/null
success "UFW firewall configured"
;;
fedora|rocky|almalinux|rhel|centos)
if command -v firewall-cmd &>/dev/null; then
firewall-cmd --permanent --add-service=ssh >/dev/null 2>&1
firewall-cmd --permanent --add-port=32400/tcp >/dev/null 2>&1 # Plex
firewall-cmd --permanent --add-port=8096/tcp >/dev/null 2>&1 # Jellyfin
firewall-cmd --reload >/dev/null 2>&1
success "firewalld configured"
fi
;;
*)
warn "Firewall configuration skipped for $OS"
;;
esac
}
# Configure Fail2Ban
configure_fail2ban() {
step "Configuring Fail2Ban..."
# Create custom jail configuration
sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
[plex]
enabled = true
port = 32400
filter = plex
logpath = /opt/arr-stack/logs/plex.log
maxretry = 5
bantime = 3600
EOF
# Create Plex filter
sudo tee /etc/fail2ban/filter.d/plex.conf > /dev/null << 'EOF'
[Definition]
failregex = .*Plex.*Failed login attempt.*<HOST>
ignoreregex =
EOF
# Restart Fail2Ban
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
success "Fail2Ban configured"
}
# Create service user
create_service_user() {
step "Creating service user..."
# Create user if it doesn't exist
if ! id "$SERVICE_USER" &>/dev/null; then
sudo useradd -r -s /bin/bash -d /home/$SERVICE_USER -m $SERVICE_USER
sudo usermod -aG docker $SERVICE_USER
# Set up sudo access for service management
echo "$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl start arr-stack, /bin/systemctl stop arr-stack, /bin/systemctl restart arr-stack, /bin/systemctl status arr-stack" | sudo tee /etc/sudoers.d/arr-stack
success "Service user '$SERVICE_USER' created"
else
info "Service user '$SERVICE_USER' already exists"
fi
}
# Install monitoring tools
install_monitoring() {
step "Installing monitoring tools..."
# Install system monitoring
sudo apt-get install -y -qq \
htop \
iotop \
nethogs \
ncdu \
tree \
lsof \
strace
# Install Docker monitoring
sudo curl -L "https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64" -o /usr/local/bin/ctop
sudo chmod +x /usr/local/bin/ctop
success "Monitoring tools installed"
}
# Create helpful aliases and scripts
# Create helpful aliases
create_aliases() {
step "Creating helpful aliases and scripts..."
log "Creating helpful aliases..."
# Create aliases file
tee ~/.bash_aliases > /dev/null << 'EOF'
# *arr Stack Management Aliases
alias arr-status='cd /opt/arr-stack && docker compose ps'
alias arr-logs='cd /opt/arr-stack && docker compose logs -f'
alias arr-restart='cd /opt/arr-stack && docker compose restart'
alias arr-update='cd /opt/arr-stack && docker compose pull && docker compose up -d'
alias arr-deploy='cd /opt/arr-stack && ./deploy.sh'
alias arr-backup='cd /opt/arr-stack && ./scripts/backup.sh'
cat > /etc/profile.d/server-aliases.sh << 'EOF'
# Server management aliases
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dlogs='docker compose logs -f'
alias dstop='docker compose stop'
alias dstart='docker compose up -d'
alias drestart='docker compose restart'
alias dupdate='docker compose pull && docker compose up -d'
# System monitoring
alias sysinfo='echo "=== System Info ===" && uname -a && echo && echo "=== Memory ===" && free -h && echo && echo "=== Disk ===" && df -h && echo && echo "=== Docker ===" && docker system df'
alias containers='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias logs-tail='docker compose logs -f --tail=100'
# Network tools
# System info
alias sysinfo='echo "=== System ===" && uname -a && echo && free -h && echo && df -h /'
alias myip='curl -s ifconfig.me'
alias ports='sudo netstat -tulpn | grep LISTEN'
alias vpn-status='docker exec gluetun curl -s ifconfig.me'
# Quick navigation
alias arr='cd /opt/arr-stack'
alias logs='cd /opt/arr-stack/logs'
alias configs='cd /opt/arr-stack/configs'
alias ports='ss -tulpn | grep LISTEN'
EOF
# Source aliases
echo 'source ~/.bash_aliases' >> ~/.bashrc
success "Helpful aliases created"
chmod +x /etc/profile.d/server-aliases.sh
success "Aliases created (reload shell to use)"
}
# Display final instructions
show_final_instructions() {
echo
echo "🎉 =============================================="
echo "🎉 *arr Media Stack Bootstrap Complete!"
echo "🎉 =============================================="
echo
echo "📋 Next Steps:"
echo
echo "1. 🔐 Configure your secrets:"
echo " cd $INSTALL_DIR"
echo " ansible-vault edit group_vars/all/vault.yml"
echo " # Add your VPN credentials and other secrets"
echo
echo "2. 🌐 Connect to Tailscale (recommended):"
echo " sudo tailscale up"
echo " # Follow the authentication link"
echo
echo "3. 🚀 Deploy the stack:"
echo " cd $INSTALL_DIR"
echo " ./deploy.sh"
echo
echo "4. 🔧 Access your services:"
echo " - Get your Tailscale IP: tailscale ip -4"
echo " - Prowlarr: http://TAILSCALE_IP:9696"
echo " - Sonarr: http://TAILSCALE_IP:8989"
echo " - Radarr: http://TAILSCALE_IP:7878"
echo " - Plex: http://$(curl -s ifconfig.me):32400"
echo
echo "📖 Documentation:"
echo " - Full guide: $INSTALL_DIR/ANSIBLE_DEPLOYMENT.md"
echo " - Configuration: $INSTALL_DIR/README.md"
echo
echo "🔧 Useful commands:"
echo " arr-status # Check container status"
echo " arr-logs # View logs"
echo " arr-restart # Restart services"
echo " arr-update # Update containers"
echo " sysinfo # System information"
echo
echo "⚠️ Important:"
echo " - Reboot or logout/login to apply group changes"
echo " - Configure your VPN credentials before deploying"
echo " - Set up indexers in Prowlarr after deployment"
echo
echo "🎯 Ready to deploy your media automation empire!"
echo
}
# Main execution
main() {
echo "🚀 Starting *arr Media Stack Bootstrap..."
echo "This will install and configure everything needed for your media stack."
echo
# Show completion message
show_complete() {
local IP=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')
# Confirmation
read -p "Continue with installation? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
warning "Installation cancelled by user."
exit 0
echo ""
echo "========================================"
echo " Server Bootstrap Complete!"
echo "========================================"
echo ""
echo "Installed:"
echo " ✅ Docker & Docker Compose"
if ! $SKIP_TAILSCALE; then
echo " ✅ Tailscale (run 'sudo tailscale up' to connect)"
fi
# Run all setup steps
check_root
detect_os
check_requirements
update_system
install_python
install_docker
install_ansible
install_tailscale
clone_repository
setup_configuration
configure_firewall
configure_fail2ban
create_service_user
install_monitoring
create_aliases
show_final_instructions
if ! $SKIP_FIREWALL; then
echo " ✅ Firewall (SSH, Plex, Jellyfin allowed)"
fi
echo " ✅ Essential tools (htop, git, curl, etc.)"
echo ""
echo "Server IP: $IP"
echo ""
echo "Next steps:"
echo " 1. Connect Tailscale: sudo tailscale up"
echo " 2. Install arr-suite:"
echo ""
echo " # Plex version:"
echo " curl -fsSL -H \"Authorization: token YOUR_TOKEN\" \\"
echo " \"https://git.vish.gg/Vish/arr-suite/raw/branch/main/install.sh\" | sudo bash"
echo ""
echo " # Jellyfin version:"
echo " curl -fsSL -H \"Authorization: token YOUR_TOKEN\" \\"
echo " \"https://git.vish.gg/Vish/arr-suite-jellyfin/raw/branch/main/install.sh\" | sudo bash"
echo ""
echo "Helpful commands:"
echo " dps - Show running containers"
echo " dlogs - View container logs"
echo " dupdate - Update all containers"
echo " sysinfo - System information"
echo " myip - Show public IP"
echo ""
}
# Handle script interruption
trap 'error "Installation interrupted. You may need to clean up manually."; exit 1' INT TERM
# Main
main() {
echo ""
echo "========================================"
echo " Server Bootstrap"
echo "========================================"
echo ""
detect_os
install_essentials
install_docker
install_tailscale
configure_firewall
create_aliases
show_complete
}
# Run main function
main "$@"
main