🎬 ARR Suite Template Bootstrap - Complete Media Automation Stack Features: - 16 production services (Prowlarr, Sonarr, Radarr, Plex, etc.) - One-command Ansible deployment - VPN-protected downloads via Gluetun - Tailscale secure access - Production-ready security (UFW, Fail2Ban) - Automated backups and monitoring - Comprehensive documentation Ready for customization and deployment to any VPS. Co-authored-by: openhands <openhands@all-hands.dev>
524 lines
14 KiB
Bash
Executable File
524 lines
14 KiB
Bash
Executable File
#!/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.*<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_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 "$@" |