#!/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 # # Tested on: Ubuntu 20.04+, Debian 11+ # Requirements: Fresh VPS with sudo access 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 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 # Logging functions log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } 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() { if [[ -f /etc/os-release ]]; then . /etc/os-release OS=$ID OS_VERSION=$VERSION_ID else error "Cannot detect OS. This script supports Ubuntu 20.04+ and Debian 11+" exit 1 fi 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 ;; debian) if [[ $(echo "$OS_VERSION >= 11" | bc -l) -eq 0 ]]; then error "Debian 11 or higher required. Found: $OS_VERSION" exit 1 fi ;; *) error "Unsupported OS: $OS. This script supports Ubuntu 20.04+ and Debian 11+" exit 1 ;; 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" } # 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 fi } # Install Tailscale (optional but recommended) install_tailscale() { step "Installing Tailscale..." # 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" 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" } # Configure firewall configure_firewall() { step "Configuring UFW firewall..." # Reset UFW to defaults sudo ufw --force reset # 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" } # 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.* 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_aliases() { step "Creating helpful aliases and scripts..." # 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' # 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 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' EOF # Source aliases echo 'source ~/.bash_aliases' >> ~/.bashrc success "Helpful aliases created" } # 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 # Confirmation read -p "Continue with installation? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then warning "Installation cancelled by user." exit 0 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 } # Handle script interruption trap 'error "Installation interrupted. You may need to clean up manually."; exit 1' INT TERM # Run main function main "$@"