Last active
December 19, 2025 22:33
-
-
Save benlacey57/d7b3a68228b77191e57296aa1a0f5d0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| #============================================================================== | |
| # Complete Ubuntu Server Setup Script | |
| # - System hardening | |
| # - SSH/SFTP for ubuntu user | |
| # - Docker installation | |
| # - Hostname: media.home | |
| # - SSL certificate with mkcert | |
| # - Cockpit web admin | |
| # - Tailscale VPN | |
| #============================================================================== | |
| set -euo pipefail | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| CYAN='\033[0;36m' | |
| MAGENTA='\033[0;35m' | |
| NC='\033[0m' | |
| # Configuration | |
| HOSTNAME="media" | |
| DOMAIN="home" | |
| FQDN="${HOSTNAME}.${DOMAIN}" | |
| UBUNTU_USER="ubuntu" | |
| SSH_PORT="22" | |
| NEW_SSH_PORT="2222" | |
| LOG_FILE="/var/log/media-server-setup.log" | |
| ERROR_LOG="/var/log/media-server-setup-errors.log" | |
| # Error tracking | |
| ERRORS_OCCURRED=0 | |
| WARNINGS_OCCURRED=0 | |
| #============================================================================== | |
| # Utility Functions | |
| #============================================================================== | |
| log() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" | |
| } | |
| log_error() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" "$ERROR_LOG" >&2 | |
| ((ERRORS_OCCURRED++)) | |
| } | |
| log_warning() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $*" | tee -a "$LOG_FILE" | |
| ((WARNINGS_OCCURRED++)) | |
| } | |
| print_header() { | |
| clear | |
| echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" | |
| echo -e "${GREEN}║ Media Server Complete Setup ║${NC}" | |
| echo -e "${GREEN}║ Ubuntu 22.04/24.04 LTS ║${NC}" | |
| echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" | |
| echo "" | |
| } | |
| print_section() { | |
| echo "" | |
| echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
| echo -e "${CYAN} $1${NC}" | |
| echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
| echo "" | |
| } | |
| print_step() { | |
| echo -e "${BLUE}→${NC} $1" | |
| } | |
| print_success() { | |
| echo -e "${GREEN}✓${NC} $1" | |
| } | |
| print_warning() { | |
| echo -e "${YELLOW}⚠${NC} $1" | |
| } | |
| print_error() { | |
| echo -e "${RED}✗${NC} $1" | |
| } | |
| check_root() { | |
| if [[ $EUID -ne 0 ]]; then | |
| print_error "This script must be run as root" | |
| echo "Run: sudo bash $0" | |
| exit 1 | |
| fi | |
| } | |
| check_os() { | |
| if [[ ! -f /etc/os-release ]]; then | |
| print_error "Cannot detect OS" | |
| exit 1 | |
| fi | |
| . /etc/os-release | |
| if [[ "$ID" != "ubuntu" ]]; then | |
| print_error "This script is designed for Ubuntu" | |
| echo "Detected: $PRETTY_NAME" | |
| exit 1 | |
| fi | |
| log "OS detected: $PRETTY_NAME" | |
| } | |
| backup_file() { | |
| local file=$1 | |
| if [[ -f "$file" ]]; then | |
| cp "$file" "${file}.backup.$(date +%Y%m%d-%H%M%S)" | |
| log "Backed up: $file" | |
| fi | |
| } | |
| handle_error() { | |
| local exit_code=$? | |
| local line_number=$1 | |
| if [[ $exit_code -ne 0 ]]; then | |
| print_error "Error occurred at line $line_number (exit code: $exit_code)" | |
| log_error "Error at line $line_number with exit code $exit_code" | |
| echo "" | |
| read -p "Continue anyway? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Installation aborted." | |
| exit $exit_code | |
| fi | |
| fi | |
| } | |
| trap 'handle_error $LINENO' ERR | |
| #============================================================================== | |
| # Pre-Installation Checks | |
| #============================================================================== | |
| pre_install_checks() { | |
| print_section "Pre-Installation Checks" | |
| check_root | |
| check_os | |
| # Check internet | |
| print_step "Checking internet connectivity..." | |
| if ping -c 1 8.8.8.8 &> /dev/null; then | |
| print_success "Internet connection available" | |
| else | |
| print_warning "No internet connection detected" | |
| fi | |
| # Check disk space | |
| local available=$(df -BG / | tail -1 | awk '{print $4}' | sed 's/G//') | |
| if [[ $available -lt 20 ]]; then | |
| print_warning "Low disk space: ${available}GB available (recommend 50GB+)" | |
| else | |
| print_success "Disk space: ${available}GB available" | |
| fi | |
| # Check RAM | |
| local ram=$(free -g | awk '/^Mem:/{print $2}') | |
| if [[ $ram -lt 2 ]]; then | |
| print_warning "Low RAM: ${ram}GB (recommend 4GB+)" | |
| else | |
| print_success "RAM: ${ram}GB" | |
| fi | |
| # Confirm configuration | |
| echo "" | |
| echo -e "${YELLOW}Configuration Summary:${NC}" | |
| echo " Hostname: ${FQDN}" | |
| echo " SSH Port: ${NEW_SSH_PORT}" | |
| echo " Ubuntu User: ${UBUNTU_USER}" | |
| echo " Services: Docker, Cockpit, Tailscale" | |
| echo " SSL: Self-signed via mkcert" | |
| echo "" | |
| read -p "Proceed with installation? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Installation cancelled" | |
| exit 0 | |
| fi | |
| log "Pre-installation checks completed" | |
| } | |
| #============================================================================== | |
| # System Updates | |
| #============================================================================== | |
| update_system() { | |
| print_section "System Updates" | |
| print_step "Updating package lists..." | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -qq 2>&1 | tee -a "$LOG_FILE" || log_warning "Package update had issues" | |
| print_success "Package lists updated" | |
| print_step "Upgrading installed packages..." | |
| apt-get upgrade -y -qq 2>&1 | tee -a "$LOG_FILE" || log_warning "Package upgrade had issues" | |
| print_success "Packages upgraded" | |
| log "System updates completed" | |
| } | |
| #============================================================================== | |
| # Set Hostname | |
| #============================================================================== | |
| set_hostname() { | |
| print_section "Hostname Configuration" | |
| print_step "Setting hostname to: ${FQDN}" | |
| # Set hostname | |
| hostnamectl set-hostname "${HOSTNAME}" 2>&1 | tee -a "$LOG_FILE" | |
| # Update /etc/hosts | |
| backup_file /etc/hosts | |
| sed -i "/127.0.1.1/d" /etc/hosts | |
| cat >> /etc/hosts << EOF | |
| 127.0.1.1 ${FQDN} ${HOSTNAME} | |
| EOF | |
| # Update /etc/hostname | |
| echo "${HOSTNAME}" > /etc/hostname | |
| print_success "Hostname configured: ${CYAN}${FQDN}${NC}" | |
| log "Hostname configuration completed" | |
| } | |
| #============================================================================== | |
| # Install Essential Packages | |
| #============================================================================== | |
| install_essential_packages() { | |
| print_section "Installing Essential Packages" | |
| print_step "Installing system packages..." | |
| export DEBIAN_FRONTEND=noninteractive | |
| local packages=( | |
| # Core utilities | |
| "apt-transport-https" | |
| "ca-certificates" | |
| "curl" | |
| "wget" | |
| "gnupg" | |
| "lsb-release" | |
| "software-properties-common" | |
| # Development tools | |
| "build-essential" | |
| "git" | |
| "vim" | |
| "nano" | |
| # System monitoring | |
| "htop" | |
| "iotop" | |
| "iftop" | |
| "ncdu" | |
| "net-tools" | |
| "dnsutils" | |
| "smartmontools" | |
| "lm-sensors" | |
| # Utilities | |
| "tmux" | |
| "screen" | |
| "tree" | |
| "zip" | |
| "unzip" | |
| "p7zip-full" | |
| "jq" | |
| "rsync" | |
| # Python | |
| "python3" | |
| "python3-pip" | |
| "python3-venv" | |
| # Network | |
| "openssh-server" | |
| "openssh-sftp-server" | |
| "vsftpd" | |
| # Security | |
| "ufw" | |
| "fail2ban" | |
| "unattended-upgrades" | |
| "apt-listchanges" | |
| # Libraries | |
| "libssl-dev" | |
| "libffi-dev" | |
| "libxml2-dev" | |
| "libxslt1-dev" | |
| "libjpeg-dev" | |
| "libpng-dev" | |
| "libfreetype6-dev" | |
| ) | |
| local failed_packages=() | |
| local installed_count=0 | |
| for package in "${packages[@]}"; do | |
| if dpkg -l | grep -q "^ii ${package} "; then | |
| print_success "${package} (already installed)" | |
| ((installed_count++)) | |
| else | |
| if apt-get install -y -qq "$package" 2>&1 | tee -a "$LOG_FILE"; then | |
| print_success "${package}" | |
| ((installed_count++)) | |
| else | |
| print_warning "${package} (failed - continuing)" | |
| log_warning "Package installation failed: ${package}" | |
| failed_packages+=("$package") | |
| fi | |
| fi | |
| done | |
| echo "" | |
| print_success "Packages installed: ${installed_count}/${#packages[@]}" | |
| if [[ ${#failed_packages[@]} -gt 0 ]]; then | |
| print_warning "Failed packages: ${failed_packages[*]}" | |
| fi | |
| log "Essential packages installation completed" | |
| } | |
| #============================================================================== | |
| # Create and Configure Ubuntu User | |
| #============================================================================== | |
| setup_ubuntu_user() { | |
| print_section "Ubuntu User Configuration" | |
| # Create ubuntu user if doesn't exist | |
| if ! id "$UBUNTU_USER" &>/dev/null; then | |
| print_step "Creating ubuntu user..." | |
| useradd -m -s /bin/bash -c "Ubuntu System User" "$UBUNTU_USER" 2>&1 | tee -a "$LOG_FILE" | |
| print_success "User created: ${UBUNTU_USER}" | |
| else | |
| print_success "User ${UBUNTU_USER} already exists" | |
| fi | |
| # Set password | |
| echo "" | |
| print_step "Set password for ${UBUNTU_USER}:" | |
| passwd "$UBUNTU_USER" | |
| # Add to groups | |
| print_step "Adding ${UBUNTU_USER} to system groups..." | |
| usermod -aG sudo "$UBUNTU_USER" 2>&1 | tee -a "$LOG_FILE" | |
| usermod -aG adm "$UBUNTU_USER" 2>&1 | tee -a "$LOG_FILE" | |
| usermod -aG systemd-journal "$UBUNTU_USER" 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Added to groups: sudo, adm, systemd-journal" | |
| # Setup home directory structure | |
| local user_home="/home/${UBUNTU_USER}" | |
| # SSH directory | |
| print_step "Configuring SSH directory..." | |
| mkdir -p "${user_home}/.ssh" | |
| chmod 700 "${user_home}/.ssh" | |
| touch "${user_home}/.ssh/authorized_keys" | |
| chmod 600 "${user_home}/.ssh/authorized_keys" | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/.ssh" | |
| print_success "SSH directory configured" | |
| # FTP/SFTP directories | |
| print_step "Creating FTP/SFTP directories..." | |
| mkdir -p "${user_home}/ftp"/{upload,download,shared} | |
| mkdir -p "${user_home}/backups" | |
| mkdir -p "${user_home}/scripts" | |
| mkdir -p "${user_home}/media" | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/ftp" | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/backups" | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/scripts" | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/media" | |
| print_success "FTP/SFTP directories created" | |
| # Add SSH key (optional) | |
| echo "" | |
| read -p "Add SSH public key for ${UBUNTU_USER}? (y/N): " -n 1 -r | |
| echo | |
| if [[ $REPLY =~ ^[Yy]$ ]]; then | |
| echo "" | |
| echo "Paste your SSH public key (or press Enter to skip):" | |
| read -r ssh_key | |
| if [[ -n "$ssh_key" ]]; then | |
| echo "$ssh_key" >> "${user_home}/.ssh/authorized_keys" | |
| chown "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/.ssh/authorized_keys" | |
| print_success "SSH key added" | |
| log "SSH key configured for ubuntu user" | |
| fi | |
| fi | |
| log "Ubuntu user configuration completed" | |
| } | |
| #============================================================================== | |
| # Configure SSH | |
| #============================================================================== | |
| configure_ssh() { | |
| print_section "SSH Configuration" | |
| print_step "Backing up SSH configuration..." | |
| backup_file /etc/ssh/sshd_config | |
| print_step "Creating hardened SSH configuration..." | |
| cat > /etc/ssh/sshd_config << EOF | |
| # Media Server SSH Configuration | |
| # Generated: $(date) | |
| # Network | |
| Port ${NEW_SSH_PORT} | |
| AddressFamily inet | |
| ListenAddress 0.0.0.0 | |
| # Protocol | |
| Protocol 2 | |
| # Host Keys | |
| HostKey /etc/ssh/ssh_host_ed25519_key | |
| HostKey /etc/ssh/ssh_host_rsa_key | |
| # Ciphers and algorithms | |
| KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512 | |
| Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr | |
| MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com | |
| # Authentication | |
| LoginGraceTime 30 | |
| PermitRootLogin no | |
| StrictModes yes | |
| MaxAuthTries 3 | |
| MaxSessions 10 | |
| PubkeyAuthentication yes | |
| PasswordAuthentication yes | |
| PermitEmptyPasswords no | |
| ChallengeResponseAuthentication no | |
| UsePAM yes | |
| # Disable insecure features | |
| HostbasedAuthentication no | |
| IgnoreRhosts yes | |
| X11Forwarding no | |
| PrintMotd no | |
| PrintLastLog yes | |
| TCPKeepAlive yes | |
| PermitUserEnvironment no | |
| Compression no | |
| ClientAliveInterval 300 | |
| ClientAliveCountMax 2 | |
| UseDNS no | |
| Banner /etc/ssh/banner | |
| # Logging | |
| SyslogFacility AUTH | |
| LogLevel VERBOSE | |
| # SFTP Configuration | |
| Subsystem sftp internal-sftp | |
| # Allow specific users | |
| AllowUsers ${UBUNTU_USER} | |
| # SFTP Chroot (optional - commented out) | |
| #Match User ${UBUNTU_USER} | |
| # ChrootDirectory /home/${UBUNTU_USER}/ftp | |
| # ForceCommand internal-sftp | |
| # AllowTcpForwarding no | |
| # X11Forwarding no | |
| EOF | |
| # Create SSH banner | |
| cat > /etc/ssh/banner << 'EOF' | |
| ╔════════════════════════════════════════════════════════════════╗ | |
| ║ AUTHORIZED ACCESS ONLY ║ | |
| ║ Media Server System ║ | |
| ║ ║ | |
| ║ Unauthorized access to this system is forbidden and will be ║ | |
| ║ prosecuted by law. By accessing this system, you agree that ║ | |
| ║ your actions may be monitored if unauthorized usage is ║ | |
| ║ suspected. ║ | |
| ╚════════════════════════════════════════════════════════════════╝ | |
| EOF | |
| # Test SSH config | |
| print_step "Testing SSH configuration..." | |
| if sshd -t 2>&1 | tee -a "$LOG_FILE"; then | |
| print_success "SSH configuration valid" | |
| else | |
| print_error "SSH configuration has errors!" | |
| log_error "SSH configuration test failed" | |
| return 1 | |
| fi | |
| # Restart SSH | |
| print_step "Restarting SSH service..." | |
| systemctl reload sshd 2>&1 | tee -a "$LOG_FILE" | |
| print_success "SSH service restarted" | |
| print_success "SSH configured on port ${CYAN}${NEW_SSH_PORT}${NC}" | |
| print_warning "Test connection: ${CYAN}ssh -p ${NEW_SSH_PORT} ${UBUNTU_USER}@$(hostname -I | awk '{print $1}')${NC}" | |
| log "SSH configuration completed" | |
| } | |
| #============================================================================== | |
| # Configure VSFTPD | |
| #============================================================================== | |
| configure_ftp() { | |
| print_section "FTP Server Configuration" | |
| print_step "Configuring vsftpd..." | |
| backup_file /etc/vsftpd.conf | |
| cat > /etc/vsftpd.conf << EOF | |
| # Media Server FTP Configuration | |
| # Generated: $(date) | |
| # Basic settings | |
| listen=YES | |
| listen_ipv6=NO | |
| anonymous_enable=NO | |
| local_enable=YES | |
| write_enable=YES | |
| local_umask=022 | |
| dirmessage_enable=YES | |
| use_localtime=YES | |
| xferlog_enable=YES | |
| connect_from_port_20=YES | |
| # Security | |
| chroot_local_user=NO | |
| secure_chroot_dir=/var/run/vsftpd/empty | |
| pam_service_name=vsftpd | |
| ssl_enable=NO | |
| # Performance | |
| pasv_enable=YES | |
| pasv_min_port=40000 | |
| pasv_max_port=40100 | |
| # User restrictions | |
| userlist_enable=YES | |
| userlist_file=/etc/vsftpd.userlist | |
| userlist_deny=NO | |
| # Logging | |
| xferlog_std_format=YES | |
| log_ftp_protocol=YES | |
| EOF | |
| # Create user list | |
| echo "${UBUNTU_USER}" > /etc/vsftpd.userlist | |
| # Enable and start vsftpd | |
| systemctl enable vsftpd 2>&1 | tee -a "$LOG_FILE" | |
| systemctl restart vsftpd 2>&1 | tee -a "$LOG_FILE" | |
| print_success "FTP server configured" | |
| print_success "FTP user: ${CYAN}${UBUNTU_USER}${NC}" | |
| log "FTP configuration completed" | |
| } | |
| #============================================================================== | |
| # Install Docker | |
| #============================================================================== | |
| install_docker() { | |
| print_section "Docker Installation" | |
| if command -v docker &> /dev/null; then | |
| print_success "Docker already installed" | |
| docker --version | |
| return 0 | |
| fi | |
| print_step "Removing old Docker versions..." | |
| apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true | |
| print_step "Adding Docker GPG key..." | |
| install -m 0755 -d /etc/apt/keyrings | |
| curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>&1 | tee -a "$LOG_FILE" | |
| chmod a+r /etc/apt/keyrings/docker.gpg | |
| print_success "Docker GPG key added" | |
| print_step "Adding Docker repository..." | |
| echo \ | |
| "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ | |
| $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null | |
| print_step "Updating package lists..." | |
| apt-get update -qq 2>&1 | tee -a "$LOG_FILE" | |
| print_step "Installing Docker packages..." | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y \ | |
| docker-ce \ | |
| docker-ce-cli \ | |
| containerd.io \ | |
| docker-buildx-plugin \ | |
| docker-compose-plugin 2>&1 | tee -a "$LOG_FILE" | |
| print_step "Starting Docker service..." | |
| systemctl start docker 2>&1 | tee -a "$LOG_FILE" | |
| systemctl enable docker 2>&1 | tee -a "$LOG_FILE" | |
| # Create docker group and add ubuntu user | |
| groupadd -f docker | |
| usermod -aG docker "$UBUNTU_USER" 2>&1 | tee -a "$LOG_FILE" | |
| # Configure Docker daemon | |
| print_step "Configuring Docker daemon..." | |
| mkdir -p /etc/docker | |
| cat > /etc/docker/daemon.json << 'EOF' | |
| { | |
| "log-driver": "json-file", | |
| "log-opts": { | |
| "max-size": "10m", | |
| "max-file": "3" | |
| }, | |
| "storage-driver": "overlay2" | |
| } | |
| EOF | |
| systemctl restart docker 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Docker installed successfully" | |
| docker --version | |
| docker compose version | |
| # Test Docker | |
| print_step "Testing Docker..." | |
| if docker run --rm hello-world 2>&1 | tee -a "$LOG_FILE" | grep -q "Hello from Docker"; then | |
| print_success "Docker is working correctly" | |
| else | |
| print_warning "Docker test failed" | |
| fi | |
| log "Docker installation completed" | |
| } | |
| #============================================================================== | |
| # Install and Configure mkcert | |
| #============================================================================== | |
| install_mkcert() { | |
| print_section "SSL Certificate Generation (mkcert)" | |
| print_step "Installing mkcert..." | |
| # Install mkcert | |
| if ! command -v mkcert &> /dev/null; then | |
| # Download and install mkcert | |
| curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" 2>&1 | tee -a "$LOG_FILE" | |
| chmod +x mkcert-v*-linux-amd64 | |
| mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert | |
| print_success "mkcert installed" | |
| else | |
| print_success "mkcert already installed" | |
| fi | |
| # Install local CA | |
| print_step "Installing local Certificate Authority..." | |
| mkcert -install 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Local CA installed" | |
| # Create certificates directory | |
| mkdir -p /etc/ssl/media-server | |
| cd /etc/ssl/media-server | |
| # Generate certificates | |
| print_step "Generating SSL certificates..." | |
| mkcert "${FQDN}" "*.${DOMAIN}" localhost 127.0.0.1 ::1 2>&1 | tee -a "$LOG_FILE" | |
| # Rename files for convenience | |
| mv "${FQDN}+4.pem" cert.pem 2>/dev/null || true | |
| mv "${FQDN}+4-key.pem" key.pem 2>/dev/null || true | |
| # Set permissions | |
| chmod 644 cert.pem | |
| chmod 600 key.pem | |
| print_success "SSL certificates generated" | |
| print_success "Certificate: ${CYAN}/etc/ssl/media-server/cert.pem${NC}" | |
| print_success "Private key: ${CYAN}/etc/ssl/media-server/key.pem${NC}" | |
| # Also copy for ubuntu user | |
| local user_home="/home/${UBUNTU_USER}" | |
| mkdir -p "${user_home}/.local/share/mkcert" | |
| cp -r "$(mkcert -CAROOT)"/* "${user_home}/.local/share/mkcert/" 2>/dev/null || true | |
| chown -R "${UBUNTU_USER}:${UBUNTU_USER}" "${user_home}/.local" | |
| log "mkcert installation and certificate generation completed" | |
| } | |
| #============================================================================== | |
| # Install Cockpit | |
| #============================================================================== | |
| install_cockpit() { | |
| print_section "Cockpit Installation" | |
| print_step "Installing Cockpit packages..." | |
| local cockpit_packages=( | |
| "cockpit" | |
| "cockpit-system" | |
| "cockpit-networkmanager" | |
| "cockpit-storaged" | |
| "cockpit-packagekit" | |
| ) | |
| # Optional packages | |
| local optional_packages=( | |
| "cockpit-docker" | |
| "cockpit-podman" | |
| "cockpit-machines" | |
| ) | |
| local installed=0 | |
| for package in "${cockpit_packages[@]}"; do | |
| if DEBIAN_FRONTEND=noninteractive apt-get install -y "$package" 2>&1 | tee -a "$LOG_FILE"; then | |
| ((installed++)) | |
| print_success "${package}" | |
| else | |
| print_warning "${package} (failed)" | |
| log_warning "Cockpit package failed: $package" | |
| fi | |
| done | |
| # Try optional packages | |
| for package in "${optional_packages[@]}"; do | |
| if DEBIAN_FRONTEND=noninteractive apt-get install -y "$package" 2>&1 | tee -a "$LOG_FILE"; then | |
| print_success "${package} (optional)" | |
| else | |
| print_warning "${package} (not available - skipping)" | |
| fi | |
| done | |
| if [[ $installed -eq 0 ]]; then | |
| log_error "No Cockpit packages installed" | |
| return 1 | |
| fi | |
| # Configure Cockpit | |
| print_step "Configuring Cockpit..." | |
| # Configure port | |
| mkdir -p /etc/systemd/system/cockpit.socket.d/ | |
| cat > /etc/systemd/system/cockpit.socket.d/listen.conf << EOF | |
| [Socket] | |
| ListenStream= | |
| ListenStream=9090 | |
| EOF | |
| # Configure SSL | |
| mkdir -p /etc/cockpit | |
| cat > /etc/cockpit/cockpit.conf << EOF | |
| [WebService] | |
| Origins = https://${FQDN}:9090 https://localhost:9090 | |
| ProtocolHeader = X-Forwarded-Proto | |
| AllowUnencrypted = false | |
| EOF | |
| # Link SSL certificates | |
| ln -sf /etc/ssl/media-server/cert.pem /etc/cockpit/ws-certs.d/1-cert.pem 2>/dev/null || true | |
| ln -sf /etc/ssl/media-server/key.pem /etc/cockpit/ws-certs.d/1-key.pem 2>/dev/null || true | |
| systemctl daemon-reload | |
| # Enable and start | |
| print_step "Starting Cockpit service..." | |
| systemctl enable --now cockpit.socket 2>&1 | tee -a "$LOG_FILE" | |
| sleep 2 | |
| if systemctl is-active --quiet cockpit.socket; then | |
| print_success "Cockpit is running" | |
| print_success "Access at: ${CYAN}https://$(hostname -I | awk '{print $1}'):9090${NC}" | |
| else | |
| print_warning "Cockpit failed to start" | |
| systemctl status cockpit.socket --no-pager | tee -a "$LOG_FILE" | |
| fi | |
| log "Cockpit installation completed" | |
| } | |
| #============================================================================== | |
| # Install Tailscale | |
| #============================================================================== | |
| install_tailscale() { | |
| print_section "Tailscale Installation" | |
| if command -v tailscale &> /dev/null; then | |
| print_success "Tailscale already installed" | |
| tailscale version | |
| return 0 | |
| fi | |
| print_step "Adding Tailscale repository..." | |
| # Add Tailscale's GPG key | |
| curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(lsb_release -cs).noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null 2>&1 | tee -a "$LOG_FILE" | |
| # Add Tailscale's repository | |
| curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(lsb_release -cs).tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list 2>&1 | tee -a "$LOG_FILE" | |
| # Update and install | |
| print_step "Installing Tailscale..." | |
| apt-get update -qq 2>&1 | tee -a "$LOG_FILE" | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y tailscale 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Tailscale installed" | |
| # Start Tailscale | |
| print_step "Configuring Tailscale..." | |
| echo "" | |
| echo -e "${YELLOW}Tailscale setup requires authentication${NC}" | |
| echo "You will need to:" | |
| echo " 1. Follow the URL that appears" | |
| echo " 2. Authenticate in your browser" | |
| echo " 3. Approve this device" | |
| echo "" | |
| read -p "Start Tailscale setup now? (y/N): " -n 1 -r | |
| echo | |
| if [[ $REPLY =~ ^[Yy]$ ]]; then | |
| print_step "Starting Tailscale..." | |
| # Start Tailscale with hostname | |
| tailscale up --hostname="${HOSTNAME}" --accept-routes | |
| if [[ $? -eq 0 ]]; then | |
| print_success "Tailscale is connected" | |
| # Show status | |
| echo "" | |
| print_step "Tailscale status:" | |
| tailscale status | |
| echo "" | |
| print_step "Tailscale IP:" | |
| tailscale ip -4 | |
| else | |
| print_warning "Tailscale setup incomplete" | |
| echo "Complete setup later with: sudo tailscale up --hostname=${HOSTNAME}" | |
| fi | |
| else | |
| print_warning "Tailscale not started" | |
| echo "Start later with: sudo tailscale up --hostname=${HOSTNAME}" | |
| fi | |
| log "Tailscale installation completed" | |
| } | |
| #============================================================================== | |
| # System Hardening | |
| #============================================================================== | |
| apply_system_hardening() { | |
| print_section "System Hardening" | |
| print_step "Applying security hardening..." | |
| # 1. Configure sysctl for security | |
| print_step "Configuring kernel parameters..." | |
| backup_file /etc/sysctl.conf | |
| cat >> /etc/sysctl.conf << 'EOF' | |
| #============================================================================== | |
| # Security Hardening - Added by media server setup | |
| #============================================================================== | |
| # IP Forwarding (disabled for security, enable if needed for Docker/Tailscale) | |
| net.ipv4.ip_forward = 0 | |
| net.ipv6.conf.all.forwarding = 0 | |
| # Disable IPv6 (optional - enable if you use IPv6) | |
| net.ipv6.conf.all.disable_ipv6 = 1 | |
| net.ipv6.conf.default.disable_ipv6 = 1 | |
| net.ipv6.conf.lo.disable_ipv6 = 1 | |
| # Network Security | |
| net.ipv4.conf.all.send_redirects = 0 | |
| net.ipv4.conf.default.send_redirects = 0 | |
| net.ipv4.conf.all.accept_source_route = 0 | |
| net.ipv4.conf.default.accept_source_route = 0 | |
| net.ipv4.conf.all.accept_redirects = 0 | |
| net.ipv4.conf.default.accept_redirects = 0 | |
| net.ipv4.conf.all.secure_redirects = 0 | |
| net.ipv4.conf.default.secure_redirects = 0 | |
| net.ipv4.conf.all.log_martians = 1 | |
| net.ipv4.conf.default.log_martians = 1 | |
| net.ipv4.icmp_echo_ignore_broadcasts = 1 | |
| net.ipv4.icmp_ignore_bogus_error_responses = 1 | |
| net.ipv4.conf.all.rp_filter = 1 | |
| net.ipv4.conf.default.rp_filter = 1 | |
| net.ipv4.tcp_syncookies = 1 | |
| # Kernel Security | |
| kernel.dmesg_restrict = 1 | |
| kernel.kptr_restrict = 2 | |
| kernel.yama.ptrace_scope = 1 | |
| kernel.unprivileged_bpf_disabled = 1 | |
| net.core.bpf_jit_harden = 2 | |
| # File System Security | |
| fs.protected_hardlinks = 1 | |
| fs.protected_symlinks = 1 | |
| fs.suid_dumpable = 0 | |
| EOF | |
| sysctl -p > /dev/null 2>&1 | |
| print_success "Kernel parameters configured" | |
| # 2. Set file permissions on sensitive files | |
| print_step "Setting file permissions..." | |
| chmod 644 /etc/passwd | |
| chmod 644 /etc/group | |
| chmod 600 /etc/shadow | |
| chmod 600 /etc/gshadow | |
| chmod 644 /etc/hosts.allow | |
| chmod 644 /etc/hosts.deny | |
| print_success "File permissions set" | |
| # 3. Disable core dumps | |
| print_step "Disabling core dumps..." | |
| echo "* hard core 0" >> /etc/security/limits.conf | |
| cat > /etc/profile.d/disable-core-dumps.sh << 'EOF' | |
| # Disable core dumps | |
| ulimit -c 0 | |
| EOF | |
| chmod +x /etc/profile.d/disable-core-dumps.sh | |
| print_success "Core dumps disabled" | |
| # 4. Configure password policy | |
| print_step "Configuring password policy..." | |
| apt-get install -y libpam-pwquality 2>&1 | tee -a "$LOG_FILE" | |
| backup_file /etc/security/pwquality.conf | |
| cat > /etc/security/pwquality.conf << 'EOF' | |
| # Password quality requirements | |
| minlen = 12 | |
| dcredit = -1 | |
| ucredit = -1 | |
| lcredit = -1 | |
| ocredit = -1 | |
| minclass = 3 | |
| maxrepeat = 3 | |
| maxclassrepeat = 2 | |
| EOF | |
| print_success "Password policy configured" | |
| # 5. Configure login security | |
| print_step "Configuring login security..." | |
| backup_file /etc/login.defs | |
| # Set password aging | |
| sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs | |
| sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs | |
| sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 7/' /etc/login.defs | |
| print_success "Login security configured" | |
| # 6. Disable unused filesystems | |
| print_step "Disabling unused filesystems..." | |
| cat > /etc/modprobe.d/disable-filesystems.conf << 'EOF' | |
| install cramfs /bin/true | |
| install freevxfs /bin/true | |
| install jffs2 /bin/true | |
| install hfs /bin/true | |
| install hfsplus /bin/true | |
| install udf /bin/true | |
| EOF | |
| print_success "Unused filesystems disabled" | |
| # 7. Configure audit logging | |
| print_step "Configuring audit logging..." | |
| apt-get install -y auditd audispd-plugins 2>&1 | tee -a "$LOG_FILE" || true | |
| if command -v auditctl &> /dev/null; then | |
| systemctl enable auditd 2>&1 | tee -a "$LOG_FILE" | |
| systemctl start auditd 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Audit logging enabled" | |
| else | |
| print_warning "Audit logging not available" | |
| fi | |
| # 8. Disable unused services | |
| print_step "Disabling unused services..." | |
| local services_to_disable=( | |
| "avahi-daemon" | |
| "cups" | |
| "isc-dhcp-server" | |
| "isc-dhcp-server6" | |
| "rpcbind" | |
| "rsync" | |
| ) | |
| for service in "${services_to_disable[@]}"; do | |
| if systemctl is-enabled "$service" &>/dev/null; then | |
| systemctl disable "$service" 2>/dev/null || true | |
| systemctl stop "$service" 2>/dev/null || true | |
| fi | |
| done | |
| print_success "Unused services disabled" | |
| log "System hardening completed" | |
| } | |
| #============================================================================== | |
| # Configure Firewall (UFW) | |
| #============================================================================== | |
| configure_firewall() { | |
| print_section "Firewall Configuration" | |
| print_step "Configuring UFW firewall..." | |
| # Install UFW if not present | |
| if ! command -v ufw &> /dev/null; then | |
| apt-get install -y ufw 2>&1 | tee -a "$LOG_FILE" | |
| fi | |
| # Reset UFW to default | |
| ufw --force reset 2>&1 | tee -a "$LOG_FILE" | |
| # Default policies | |
| ufw default deny incoming | |
| ufw default allow outgoing | |
| # Allow SSH (custom port) | |
| ufw allow ${NEW_SSH_PORT}/tcp comment 'SSH' | |
| print_success "SSH port ${NEW_SSH_PORT} allowed" | |
| # Allow SFTP (same as SSH) | |
| print_success "SFTP (port ${NEW_SSH_PORT}) allowed" | |
| # Allow FTP | |
| ufw allow 21/tcp comment 'FTP' | |
| ufw allow 40000:40100/tcp comment 'FTP Passive' | |
| print_success "FTP ports allowed" | |
| # Allow Cockpit | |
| ufw allow 9090/tcp comment 'Cockpit' | |
| print_success "Cockpit port 9090 allowed" | |
| # Allow HTTP/HTTPS | |
| ufw allow 80/tcp comment 'HTTP' | |
| ufw allow 443/tcp comment 'HTTPS' | |
| print_success "HTTP/HTTPS ports allowed" | |
| # Allow common media server ports | |
| ufw allow 8096/tcp comment 'Jellyfin' | |
| ufw allow 32400/tcp comment 'Plex' | |
| ufw allow 8989/tcp comment 'Sonarr' | |
| ufw allow 7878/tcp comment 'Radarr' | |
| ufw allow 8686/tcp comment 'Lidarr' | |
| ufw allow 9000/tcp comment 'Portainer' | |
| print_success "Media server ports allowed" | |
| # Allow Tailscale | |
| ufw allow 41641/udp comment 'Tailscale' | |
| print_success "Tailscale port allowed" | |
| # Rate limiting for SSH | |
| ufw limit ${NEW_SSH_PORT}/tcp comment 'SSH rate limit' | |
| # Enable UFW | |
| ufw --force enable 2>&1 | tee -a "$LOG_FILE" | |
| print_success "Firewall enabled and configured" | |
| echo "" | |
| print_step "Firewall rules:" | |
| ufw status numbered | tee -a "$LOG_FILE" | |
| log "Firewall configuration completed" | |
| } | |
| #============================================================================== | |
| # Configure Fail2ban | |
| #============================================================================== | |
| configure_fail2ban() { | |
| print_section "Fail2ban Configuration" | |
| print_step "Configuring Fail2ban..." | |
| # Create local configuration | |
| cat > /etc/fail2ban/jail.local << EOF | |
| [DEFAULT] | |
| # Ban settings | |
| bantime = 1h | |
| findtime = 10m | |
| maxretry = 5 | |
| banaction = ufw | |
| # Email notifications (configure if needed) | |
| destemail = root@localhost | |
| sendername = Fail2ban-${HOSTNAME} | |
| action = %(action_mwl)s | |
| [sshd] | |
| enabled = true | |
| port = ${NEW_SSH_PORT} | |
| logpath = /var/log/auth.log | |
| maxretry = 3 | |
| bantime = 24h | |
| findtime = 10m | |
| [sshd-ddos] | |
| enabled = true | |
| port = ${NEW_SSH_PORT} | |
| logpath = /var/log/auth.log | |
| maxretry = 10 | |
| findtime = 60 | |
| [cockpit] | |
| enabled = true | |
| port = 9090 | |
| logpath = /var/log/auth.log | |
| maxretry = 5 | |
| bantime = 1h | |
| [vsftpd] | |
| enabled = true | |
| port = 21 | |
| logpath = /var/log/vsftpd.log | |
| maxretry = 3 | |
| bantime = 1h | |
| EOF | |
| # Start and enable Fail2ban | |
| systemctl enable fail2ban 2>&1 | tee -a "$LOG_FILE" | |
| systemctl restart fail2ban 2>&1 | tee -a "$LOG_FILE" | |
| sleep 2 | |
| if systemctl is-active --quiet fail2ban; then | |
| print_success "Fail2ban is running" | |
| # Show status | |
| echo "" | |
| fail2ban-client status | tee -a "$LOG_FILE" | |
| else | |
| print_warning "Fail2ban failed to start" | |
| fi | |
| log "Fail2ban configuration completed" | |
| } | |
| #============================================================================== | |
| # Configure Automatic Updates | |
| #============================================================================== | |
| configure_automatic_updates() { | |
| print_section "Automatic Security Updates" | |
| print_step "Configuring unattended-upgrades..." | |
| # Configure automatic updates | |
| cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF' | |
| Unattended-Upgrade::Allowed-Origins { | |
| "${distro_id}:${distro_codename}"; | |
| "${distro_id}:${distro_codename}-security"; | |
| "${distro_id}ESMApps:${distro_codename}-apps-security"; | |
| "${distro_id}ESM:${distro_codename}-infra-security"; | |
| }; | |
| Unattended-Upgrade::DevRelease "false"; | |
| Unattended-Upgrade::AutoFixInterruptedDpkg "true"; | |
| Unattended-Upgrade::MinimalSteps "true"; | |
| Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; | |
| Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; | |
| Unattended-Upgrade::Remove-Unused-Dependencies "true"; | |
| Unattended-Upgrade::Automatic-Reboot "false"; | |
| Unattended-Upgrade::Automatic-Reboot-Time "03:00"; | |
| Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; | |
| EOF | |
| # Enable automatic updates | |
| cat > /etc/apt/apt.conf.d/20auto-upgrades << 'EOF' | |
| APT::Periodic::Update-Package-Lists "1"; | |
| APT::Periodic::Download-Upgradeable-Packages "1"; | |
| APT::Periodic::AutocleanInterval "7"; | |
| APT::Periodic::Unattended-Upgrade "1"; | |
| EOF | |
| print_success "Automatic security updates configured" | |
| log "Automatic updates configuration completed" | |
| } | |
| #============================================================================== | |
| # Create Management Scripts | |
| #============================================================================== | |
| create_management_scripts() { | |
| print_section "Creating Management Scripts" | |
| print_step "Creating system management scripts..." | |
| # Create scripts directory | |
| mkdir -p /usr/local/bin/media-server | |
| # 1. System Info Script | |
| cat > /usr/local/bin/media-server/system-info.sh << 'SYSINFO_EOF' | |
| #!/bin/bash | |
| GREEN='\033[0;32m' | |
| CYAN='\033[0;36m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' | |
| echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" | |
| echo -e "${GREEN}║ Media Server System Information ║${NC}" | |
| echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" | |
| echo "" | |
| # System | |
| echo -e "${CYAN}System:${NC}" | |
| echo " Hostname: $(hostname -f)" | |
| echo " OS: $(lsb_release -d | cut -f2)" | |
| echo " Kernel: $(uname -r)" | |
| echo " Uptime: $(uptime -p)" | |
| echo "" | |
| # Network | |
| echo -e "${CYAN}Network:${NC}" | |
| ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | while read ip; do | |
| echo " IP: $ip" | |
| done | |
| if command -v tailscale &> /dev/null; then | |
| TS_IP=$(tailscale ip -4 2>/dev/null || echo "Not connected") | |
| echo " Tailscale IP: $TS_IP" | |
| fi | |
| echo "" | |
| # Services | |
| echo -e "${CYAN}Services:${NC}" | |
| systemctl is-active --quiet sshd && echo " ✓ SSH" || echo " ✗ SSH" | |
| systemctl is-active --quiet docker && echo " ✓ Docker" || echo " ✗ Docker" | |
| systemctl is-active --quiet cockpit.socket && echo " ✓ Cockpit" || echo " ✗ Cockpit" | |
| systemctl is-active --quiet vsftpd && echo " ✓ FTP" || echo " ✗ FTP" | |
| systemctl is-active --quiet fail2ban && echo " ✓ Fail2ban" || echo " ✗ Fail2ban" | |
| systemctl is-active --quiet tailscaled && echo " ✓ Tailscale" || echo " ✗ Tailscale" | |
| echo "" | |
| # Docker | |
| if command -v docker &> /dev/null; then | |
| echo -e "${CYAN}Docker:${NC}" | |
| echo " Version: $(docker --version | awk '{print $3}' | sed 's/,//')" | |
| echo " Containers: $(docker ps -q | wc -l) running" | |
| echo "" | |
| fi | |
| # Resources | |
| echo -e "${CYAN}Resources:${NC}" | |
| df -h / | tail -1 | awk '{printf " Disk: %s / %s (%s used)\n", $3, $2, $5}' | |
| free -h | grep "Mem:" | awk '{printf " RAM: %s / %s\n", $3, $2}' | |
| echo " Load: $(uptime | awk -F'load average:' '{print $2}')" | |
| echo "" | |
| # SSL | |
| if [[ -f /etc/ssl/media-server/cert.pem ]]; then | |
| echo -e "${CYAN}SSL Certificate:${NC}" | |
| echo " Location: /etc/ssl/media-server/" | |
| EXPIRY=$(openssl x509 -enddate -noout -in /etc/ssl/media-server/cert.pem | cut -d= -f2) | |
| echo " Expires: $EXPIRY" | |
| echo "" | |
| fi | |
| # Access URLs | |
| echo -e "${CYAN}Access URLs:${NC}" | |
| LOCAL_IP=$(ip route get 1 | awk '{print $7}' | head -1) | |
| echo " Cockpit: https://${LOCAL_IP}:9090" | |
| echo " SSH: ssh -p 2222 ubuntu@${LOCAL_IP}" | |
| echo " SFTP: sftp -P 2222 ubuntu@${LOCAL_IP}" | |
| echo " FTP: ftp://${LOCAL_IP}" | |
| echo "" | |
| SYSINFO_EOF | |
| chmod +x /usr/local/bin/media-server/system-info.sh | |
| ln -sf /usr/local/bin/media-server/system-info.sh /usr/local/bin/system-info | |
| # 2. Firewall Management Script | |
| cat > /usr/local/bin/media-server/firewall-manage.sh << 'FW_EOF' | |
| #!/bin/bash | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "This script must be run as root" | |
| exit 1 | |
| fi | |
| case "${1:-status}" in | |
| status) | |
| ufw status numbered | |
| ;; | |
| allow) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 allow <port/service>" | |
| exit 1 | |
| fi | |
| ufw allow "$2" | |
| ;; | |
| deny) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 deny <port/service>" | |
| exit 1 | |
| fi | |
| ufw deny "$2" | |
| ;; | |
| delete) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 delete <rule-number>" | |
| exit 1 | |
| fi | |
| ufw delete "$2" | |
| ;; | |
| *) | |
| echo "Usage: $0 {status|allow|deny|delete} [argument]" | |
| exit 1 | |
| ;; | |
| esac | |
| FW_EOF | |
| chmod +x /usr/local/bin/media-server/firewall-manage.sh | |
| ln -sf /usr/local/bin/media-server/firewall-manage.sh /usr/local/bin/firewall | |
| # 3. Docker Quick Commands | |
| cat > /usr/local/bin/media-server/docker-quick.sh << 'DOCKER_EOF' | |
| #!/bin/bash | |
| case "${1:-help}" in | |
| ps) | |
| docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | |
| ;; | |
| logs) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 logs <container-name>" | |
| exit 1 | |
| fi | |
| docker logs -f "$2" | |
| ;; | |
| restart) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 restart <container-name>" | |
| exit 1 | |
| fi | |
| docker restart "$2" | |
| ;; | |
| stop) | |
| if [[ -z "$2" ]]; then | |
| echo "Usage: $0 stop <container-name>" | |
| exit 1 | |
| fi | |
| docker stop "$2" | |
| ;; | |
| stats) | |
| docker stats | |
| ;; | |
| clean) | |
| docker system prune -af | |
| ;; | |
| *) | |
| cat << 'EOF' | |
| Docker Quick Commands: | |
| ps List running containers | |
| logs <container> View container logs | |
| restart <container> Restart a container | |
| stop <container> Stop a container | |
| stats Show resource usage | |
| clean Clean unused images/containers | |
| EOF | |
| ;; | |
| esac | |
| DOCKER_EOF | |
| chmod +x /usr/local/bin/media-server/docker-quick.sh | |
| ln -sf /usr/local/bin/media-server/docker-quick.sh /usr/local/bin/dkr | |
| print_success "Management scripts created" | |
| print_success "Commands: ${CYAN}system-info${NC}, ${CYAN}firewall${NC}, ${CYAN}dkr${NC}" | |
| log "Management scripts creation completed" | |
| } | |
| #============================================================================== | |
| # Configure MOTD | |
| #============================================================================== | |
| configure_motd() { | |
| print_section "Message of the Day Configuration" | |
| print_step "Configuring MOTD..." | |
| # Disable default MOTD scripts | |
| chmod -x /etc/update-motd.d/* 2>/dev/null || true | |
| # Create custom MOTD | |
| cat > /etc/update-motd.d/00-header << 'MOTD_EOF' | |
| #!/bin/bash | |
| GREEN='\033[0;32m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' | |
| echo -e "${GREEN}" | |
| cat << 'EOF' | |
| ╔════════════════════════════════════════════════════════════════╗ | |
| ║ Welcome to Media Server ║ | |
| ║ media.home ║ | |
| ╚════════════════════════════════════════════════════════════════╝ | |
| EOF | |
| echo -e "${NC}" | |
| MOTD_EOF | |
| cat > /etc/update-motd.d/10-sysinfo << 'MOTD_SYSINFO_EOF' | |
| #!/bin/bash | |
| CYAN='\033[0;36m' | |
| GREEN='\033[0;32m' | |
| NC='\033[0m' | |
| # Get info | |
| LOCAL_IP=$(ip route get 1 | awk '{print $7}' | head -1) | |
| CONTAINERS=$(docker ps -q 2>/dev/null | wc -l) | |
| echo -e "${CYAN}System Information:${NC}" | |
| echo " IP Address: $LOCAL_IP" | |
| echo " Uptime: $(uptime -p)" | |
| echo " Docker Containers: $CONTAINERS running" | |
| echo "" | |
| echo -e "${CYAN}Quick Commands:${NC}" | |
| echo " ${GREEN}system-info${NC} - Full system information" | |
| echo " ${GREEN}firewall${NC} - Manage firewall rules" | |
| echo " ${GREEN}dkr ps${NC} - List Docker containers" | |
| echo "" | |
| echo -e "${CYAN}Access:${NC}" | |
| echo " Cockpit: https://${LOCAL_IP}:9090" | |
| echo "" | |
| MOTD_SYSINFO_EOF | |
| chmod +x /etc/update-motd.d/00-header | |
| chmod +x /etc/update-motd.d/10-sysinfo | |
| print_success "MOTD configured" | |
| log "MOTD configuration completed" | |
| } | |
| #============================================================================== | |
| # Final Summary | |
| #============================================================================== | |
| print_completion_summary() { | |
| print_section "Installation Complete!" | |
| local LOCAL_IP=$(ip route get 1 | awk '{print $7}' | head -1) | |
| local TS_IP=$(tailscale ip -4 2>/dev/null || echo "Not configured") | |
| echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" | |
| echo -e "${GREEN}║ Media Server Setup Complete! ║${NC}" | |
| echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" | |
| echo "" | |
| echo -e "${CYAN}System Configuration:${NC}" | |
| echo " Hostname: ${FQDN}" | |
| echo " Local IP: ${LOCAL_IP}" | |
| echo " Tailscale IP: ${TS_IP}" | |
| echo "" | |
| echo -e "${CYAN}Access Information:${NC}" | |
| echo " SSH: ssh -p ${NEW_SSH_PORT} ${UBUNTU_USER}@${LOCAL_IP}" | |
| echo " SFTP: sftp -P ${NEW_SSH_PORT} ${UBUNTU_USER}@${LOCAL_IP}" | |
| echo " FTP: ftp://${LOCAL_IP}" | |
| echo " Cockpit: https://${LOCAL_IP}:9090" | |
| echo "" | |
| echo -e "${CYAN}SSL Certificates:${NC}" | |
| echo " Location: /etc/ssl/media-server/" | |
| echo " Certificate: cert.pem" | |
| echo " Private Key: key.pem" | |
| echo "" | |
| echo -e "${CYAN}Installed Services:${NC}" | |
| echo " ✓ SSH/SFTP (Port ${NEW_SSH_PORT})" | |
| echo " ✓ FTP (Port 21)" | |
| echo " ✓ Docker & Docker Compose" | |
| echo " ✓ Cockpit Web Admin" | |
| echo " ✓ Tailscale VPN" | |
| echo " ✓ UFW Firewall" | |
| echo " ✓ Fail2ban" | |
| echo " ✓ Automatic Updates" | |
| echo "" | |
| echo -e "${CYAN}Security Features:${NC}" | |
| echo " ✓ System hardening applied" | |
| echo " ✓ Firewall configured" | |
| echo " ✓ Fail2ban active" | |
| echo " ✓ Root login disabled" | |
| echo " ✓ Password policy enforced" | |
| echo " ✓ Automatic security updates" | |
| echo "" | |
| echo -e "${CYAN}Quick Commands:${NC}" | |
| echo " system-info - System status" | |
| echo " firewall - Manage firewall" | |
| echo " dkr ps - Docker containers" | |
| echo "" | |
| echo -e "${CYAN}Log Files:${NC}" | |
| echo " Main: ${LOG_FILE}" | |
| echo " Errors: ${ERROR_LOG}" | |
| echo "" | |
| if [[ $ERRORS_OCCURRED -gt 0 ]]; then | |
| echo -e "${YELLOW}⚠ ${ERRORS_OCCURRED} error(s) occurred during installation${NC}" | |
| echo -e "${YELLOW} Check logs for details${NC}" | |
| echo "" | |
| fi | |
| if [[ $WARNINGS_OCCURRED -gt 0 ]]; then | |
| echo -e "${YELLOW}⚠ ${WARNINGS_OCCURRED} warning(s) occurred during installation${NC}" | |
| echo "" | |
| fi | |
| echo -e "${YELLOW}Important Notes:${NC}" | |
| echo " 1. Test SSH connection: ssh -p ${NEW_SSH_PORT} ${UBUNTU_USER}@${LOCAL_IP}" | |
| echo " 2. Logout and login as ${UBUNTU_USER} to use Docker without sudo" | |
| echo " 3. Configure Tailscale if not done: sudo tailscale up" | |
| echo " 4. Access Cockpit at: https://${LOCAL_IP}:9090" | |
| echo " 5. SSL certificate trusted only on this machine (mkcert CA)" | |
| echo "" | |
| echo -e "${YELLOW}Next Steps:${NC}" | |
| echo " 1. Test all access methods" | |
| echo " 2. Deploy Docker containers" | |
| echo " 3. Configure media services" | |
| echo " 4. Set up automated backups" | |
| echo "" | |
| log "Installation completed successfully" | |
| log "Errors: ${ERRORS_OCCURRED}, Warnings: ${WARNINGS_OCCURRED}" | |
| } | |
| #============================================================================== | |
| # Main Installation Flow | |
| #============================================================================== | |
| main() { | |
| print_header | |
| # Pre-checks | |
| pre_install_checks | |
| # System setup | |
| update_system | |
| set_hostname | |
| install_essential_packages | |
| # User setup | |
| setup_ubuntu_user | |
| # Network services | |
| configure_ssh | |
| configure_ftp | |
| # Core services | |
| install_docker | |
| install_mkcert | |
| install_cockpit | |
| install_tailscale | |
| # Security | |
| apply_system_hardening | |
| configure_firewall | |
| configure_fail2ban | |
| configure_automatic_updates | |
| # Management | |
| create_management_scripts | |
| configure_motd | |
| # Final summary | |
| print_completion_summary | |
| echo "" | |
| echo -e "${GREEN}Setup complete!${NC}" | |
| echo "" | |
| echo -e "${YELLOW}Recommended: Reboot to apply all changes${NC}" | |
| read -p "Reboot now? (y/N): " -n 1 -r | |
| echo | |
| if [[ $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Rebooting in 10 seconds... (Ctrl+C to cancel)" | |
| sleep 10 | |
| reboot | |
| else | |
| echo "Please reboot manually when ready: sudo reboot" | |
| fi | |
| } | |
| # Run main installation | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment