#!/bin/bash # Media Downloader Installer Script # Version: 13.13.1 # Installs to /opt/media-downloader set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Installation directory INSTALL_DIR="/opt/media-downloader" SERVICE_NAME="media-downloader" CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Media Downloader Installer v13.13.1 ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}" echo "" # Check if running as root if [[ $EUID -ne 0 ]]; then echo -e "${RED}This script must be run as root (use sudo)${NC}" exit 1 fi # Get the actual user who ran sudo ACTUAL_USER="${SUDO_USER:-$USER}" ACTUAL_HOME=$(getent passwd "$ACTUAL_USER" | cut -d: -f6) echo -e "${YELLOW}Installation Settings:${NC}" echo " Install directory: $INSTALL_DIR" echo " Service name: $SERVICE_NAME" echo " User: $ACTUAL_USER" echo " Source: $CURRENT_DIR" echo "" read -p "Continue with installation? (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Installation cancelled" exit 1 fi # Stop services if they exist echo -e "${YELLOW}Stopping existing services...${NC}" systemctl stop $SERVICE_NAME 2>/dev/null || true systemctl stop media-downloader-api 2>/dev/null || true systemctl stop media-downloader-frontend 2>/dev/null || true systemctl stop xvfb-media-downloader 2>/dev/null || true # Create installation directory echo -e "${GREEN}Creating installation directory...${NC}" mkdir -p "$INSTALL_DIR" # Copy files echo -e "${GREEN}Copying files...${NC}" rsync -a --exclude='.git' --exclude='node_modules' --exclude='venv' --exclude='__pycache__' \ --exclude='.playwright' --exclude='dist' --exclude='*.pyc' \ "$CURRENT_DIR/" "$INSTALL_DIR/" # Create required directories echo -e "${GREEN}Creating required directories...${NC}" mkdir -p "$INSTALL_DIR/logs" mkdir -p "$INSTALL_DIR/database" mkdir -p "$INSTALL_DIR/cookies" mkdir -p "$INSTALL_DIR/sessions" mkdir -p "$INSTALL_DIR/config" mkdir -p "$INSTALL_DIR/data" mkdir -p "$INSTALL_DIR/data/face_references" # Face recognition reference images mkdir -p "$INSTALL_DIR/data/cache/profile_images" # Cached creator avatars/banners mkdir -p "/opt/immich/review" # Face recognition review queue mkdir -p "/opt/immich/recycle" # Recycle bin mkdir -p "/var/log/media-downloader" # System log directory # Set permissions echo -e "${GREEN}Setting permissions...${NC}" chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_DIR" chmod +x "$INSTALL_DIR/media-downloader.py" chmod +x "$INSTALL_DIR/scripts/"*.sh chmod +x "$INSTALL_DIR/scripts/"*.py 2>/dev/null || true # Install system dependencies echo -e "${GREEN}Installing system dependencies...${NC}" apt-get update > /dev/null 2>&1 apt-get install -y cmake build-essential libopenblas-dev liblapack-dev \ ffmpeg redis-server xvfb nodejs npm \ libheif-examples imagemagick \ postgresql postgresql-contrib libpq-dev > /dev/null 2>&1 # Start Redis if not running systemctl enable redis-server systemctl start redis-server # Setup PostgreSQL echo -e "${GREEN}Setting up PostgreSQL...${NC}" systemctl enable postgresql systemctl start postgresql sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='media_downloader'" | grep -q 1 || \ sudo -u postgres psql -c "CREATE USER media_downloader WITH PASSWORD 'changeme';" sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='media_downloader'" | grep -q 1 || \ sudo -u postgres createdb -O media_downloader media_downloader echo -e "${GREEN}✓ PostgreSQL configured (user: media_downloader, db: media_downloader)${NC}" echo -e "${YELLOW}⚠ Remember to update DATABASE_URL in .env with the correct password${NC}" # Create virtual environment echo -e "${GREEN}Creating Python virtual environment...${NC}" rm -rf "$INSTALL_DIR/venv" 2>/dev/null || true python3 -m venv "$INSTALL_DIR/venv" chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_DIR/venv" # Install Python dependencies echo -e "${GREEN}Installing Python dependencies from requirements.txt...${NC}" sudo -u "$ACTUAL_USER" "$INSTALL_DIR/venv/bin/python" -m pip install --upgrade pip sudo -u "$ACTUAL_USER" "$INSTALL_DIR/venv/bin/python" -m pip install -r "$INSTALL_DIR/requirements.txt" # Install playwright browsers echo -e "${GREEN}Installing Playwright browsers...${NC}" sudo -u "$ACTUAL_USER" bash -c "cd '$INSTALL_DIR' && '$INSTALL_DIR/venv/bin/python' -m playwright install chromium firefox" # Install frontend dependencies echo -e "${GREEN}Installing frontend dependencies...${NC}" cd "$INSTALL_DIR/web/frontend" sudo -u "$ACTUAL_USER" npm install sudo -u "$ACTUAL_USER" npm run build PYTHON_BIN="$INSTALL_DIR/venv/bin/python" # ============================================================================ # CHECK DEPENDENCIES # ============================================================================ echo "" echo -e "${BLUE}Checking Dependencies...${NC}" # Check for FlareSolverr if command -v docker &> /dev/null; then if docker ps | grep -q flaresolverr; then echo -e "${GREEN}✓ FlareSolverr container is running${NC}" else echo -e "${YELLOW}⚠ FlareSolverr container not found${NC}" read -p "Install FlareSolverr Docker container now? (recommended) (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then docker run -d \ --name flaresolverr \ -p 8191:8191 \ -e LOG_LEVEL=info \ --restart unless-stopped \ ghcr.io/flaresolverr/flaresolverr:latest echo -e "${GREEN}✓ FlareSolverr installed on port 8191${NC}" fi fi else echo -e "${YELLOW}⚠ Docker not found. FlareSolverr requires Docker for Cloudflare bypass.${NC}" fi # ============================================================================ # CREATE SYSTEMD SERVICES # ============================================================================ echo "" echo -e "${GREEN}Creating systemd services...${NC}" # 1. Xvfb Virtual Display Service cat > "/etc/systemd/system/xvfb-media-downloader.service" << EOF [Unit] Description=Xvfb Virtual Display for Media Downloader After=network.target [Service] Type=simple User=root ExecStart=/usr/bin/Xvfb :100 -screen 0 1920x1080x24 -nolisten tcp Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF # 2. Main Scheduler Service cat > "/etc/systemd/system/$SERVICE_NAME.service" << EOF [Unit] Description=Media Downloader Scheduler Service After=network.target xvfb-media-downloader.service redis-server.service Wants=xvfb-media-downloader.service [Service] Type=simple User=$ACTUAL_USER Group=$ACTUAL_USER WorkingDirectory=$INSTALL_DIR ExecStart=$PYTHON_BIN $INSTALL_DIR/media-downloader.py --scheduler Restart=on-failure RestartSec=30 StandardOutput=append:$INSTALL_DIR/logs/service.log StandardError=append:$INSTALL_DIR/logs/service.log Environment="PYTHONUNBUFFERED=1" Environment="PYTHONDONTWRITEBYTECODE=1" Environment="DISPLAY=:100" LimitNOFILE=65536 Nice=10 [Install] WantedBy=multi-user.target EOF # 3. Web API Service cat > "/etc/systemd/system/media-downloader-api.service" << EOF [Unit] Description=Media Downloader Web API After=network.target redis-server.service Wants=redis-server.service [Service] Type=simple User=root Group=root WorkingDirectory=$INSTALL_DIR/web/backend ExecStart=$PYTHON_BIN $INSTALL_DIR/web/backend/api.py Restart=always RestartSec=10 StandardOutput=journal StandardError=journal Environment="PYTHONUNBUFFERED=1" Environment="PYTHONDONTWRITEBYTECODE=1" LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 4. Web Frontend Service (Production - serves pre-built static files) # Note: For development with hot-reload, run: cd web/frontend && npm run dev cat > "/etc/systemd/system/media-downloader-frontend.service" << EOF [Unit] Description=Media Downloader Web Frontend (Production) After=network.target media-downloader-api.service Wants=media-downloader-api.service [Service] Type=simple User=root Group=root WorkingDirectory=$INSTALL_DIR/web/frontend ExecStart=/usr/bin/npm run preview -- --host 0.0.0.0 --port 5173 Restart=always RestartSec=10 StandardOutput=journal StandardError=journal Environment="NODE_ENV=production" LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 4b. Development Frontend Service (optional - hot reload) cat > "/etc/systemd/system/media-downloader-frontend-dev.service" << EOF [Unit] Description=Media Downloader Web Frontend (Development - Hot Reload) After=network.target media-downloader-api.service Wants=media-downloader-api.service [Service] Type=simple User=$ACTUAL_USER Group=$ACTUAL_USER WorkingDirectory=$INSTALL_DIR/web/frontend ExecStart=/usr/bin/npm run dev -- --host 0.0.0.0 --port 5173 Restart=always RestartSec=10 StandardOutput=journal StandardError=journal Environment="NODE_ENV=development" LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 5. Thumbnail Cache Builder Service + Timer cat > "/etc/systemd/system/media-cache-builder.service" << EOF [Unit] Description=Media Thumbnail and Metadata Cache Builder After=network.target [Service] Type=oneshot User=root WorkingDirectory=$INSTALL_DIR ExecStart=$PYTHON_BIN $INSTALL_DIR/modules/thumbnail_cache_builder.py StandardOutput=journal StandardError=journal TimeoutStartSec=3600 Nice=19 IOSchedulingClass=idle CPUQuota=50% [Install] WantedBy=multi-user.target EOF cat > "/etc/systemd/system/media-cache-builder.timer" << EOF [Unit] Description=Daily Media Cache Builder Timer Requires=media-cache-builder.service [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true RandomizedDelaySec=30min [Install] WantedBy=timers.target EOF # 6. Embedding Generator Service + Timer cat > "/etc/systemd/system/media-embedding-generator.service" << EOF [Unit] Description=Media Downloader Embedding Generator (CLIP) After=network.target [Service] Type=oneshot User=root WorkingDirectory=$INSTALL_DIR ExecStart=$PYTHON_BIN $INSTALL_DIR/scripts/generate-embeddings.py StandardOutput=journal StandardError=journal TimeoutStartSec=3600 [Install] WantedBy=multi-user.target EOF cat > "/etc/systemd/system/media-embedding-generator.timer" << EOF [Unit] Description=Nightly Media Embedding Generation Timer Requires=media-embedding-generator.service [Timer] OnCalendar=*-*-* 03:00:00 RandomizedDelaySec=1800 Persistent=true [Install] WantedBy=timers.target EOF # 7. Celebrity Enrichment Service + Timer cat > "/etc/systemd/system/media-celebrity-enrichment.service" << EOF [Unit] Description=Media Downloader Celebrity Metadata Enrichment After=network.target [Service] Type=oneshot User=root WorkingDirectory=$INSTALL_DIR ExecStart=$PYTHON_BIN $INSTALL_DIR/scripts/enrich_celebrity_metadata.py StandardOutput=journal StandardError=journal TimeoutStartSec=3600 [Install] WantedBy=multi-user.target EOF cat > "/etc/systemd/system/media-celebrity-enrichment.timer" << EOF [Unit] Description=Nightly Celebrity Metadata Enrichment Timer Requires=media-celebrity-enrichment.service [Timer] OnCalendar=*-*-* 04:00:00 RandomizedDelaySec=300 Persistent=true [Install] WantedBy=timers.target EOF # 8. Plex Matching Service + Timer cat > "/etc/systemd/system/plex-match.service" << EOF [Unit] Description=Match appearances to Plex library After=media-downloader-api.service Requires=media-downloader-api.service [Service] Type=oneshot ExecStart=$INSTALL_DIR/scripts/plex-match.sh User=root EOF cat > "/etc/systemd/system/plex-match.timer" << EOF [Unit] Description=Run Plex matching twice daily [Timer] OnCalendar=*-*-* 06:00:00 OnCalendar=*-*-* 18:00:00 Persistent=true [Install] WantedBy=timers.target EOF # 9. Database Cleanup Service + Timer cat > "/etc/systemd/system/media-downloader-db-cleanup.service" << EOF [Unit] Description=Media Downloader Database Cleanup After=network.target media-downloader-api.service [Service] Type=oneshot User=root WorkingDirectory=$INSTALL_DIR ExecStart=$INSTALL_DIR/scripts/db-cleanup.sh StandardOutput=append:$INSTALL_DIR/logs/db-cleanup.log StandardError=append:$INSTALL_DIR/logs/db-cleanup.log MemoryMax=512M CPUQuota=50% Restart=no [Install] WantedBy=multi-user.target EOF cat > "/etc/systemd/system/media-downloader-db-cleanup.timer" << EOF [Unit] Description=Media Downloader Database Cleanup Timer Requires=media-downloader-db-cleanup.service [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true OnBootSec=5min RandomizedDelaySec=10min [Install] WantedBy=timers.target EOF # Create command-line wrapper echo -e "${GREEN}Creating command-line wrapper...${NC}" cat > "/usr/local/bin/media-downloader" << EOF #!/bin/bash cd $INSTALL_DIR export DISPLAY=:100 $PYTHON_BIN $INSTALL_DIR/media-downloader.py "\$@" EOF chmod +x "/usr/local/bin/media-downloader" # Copy config if it doesn't exist if [ ! -f "$INSTALL_DIR/config/settings.json" ]; then echo -e "${GREEN}Copying default configuration...${NC}" if [ -f "$INSTALL_DIR/config/settings.example.json" ]; then cp "$INSTALL_DIR/config/settings.example.json" "$INSTALL_DIR/config/settings.json" fi chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_DIR/config" chmod 600 "$INSTALL_DIR/config/settings.json" 2>/dev/null || true fi # Reload systemd echo -e "${GREEN}Reloading systemd...${NC}" systemctl daemon-reload # ============================================================================ # ENABLE AND START SERVICES # ============================================================================ echo "" echo -e "${BLUE}Service Configuration${NC}" read -p "Enable and start all services? (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo -e "${GREEN}Enabling services...${NC}" # Core services systemctl enable xvfb-media-downloader.service systemctl enable $SERVICE_NAME.service systemctl enable media-downloader-api.service # Timers systemctl enable media-cache-builder.timer systemctl enable media-embedding-generator.timer systemctl enable media-celebrity-enrichment.timer systemctl enable plex-match.timer systemctl enable media-downloader-db-cleanup.timer echo -e "${GREEN}Starting services...${NC}" # Start in order systemctl start xvfb-media-downloader.service sleep 2 systemctl start media-downloader-api.service sleep 2 systemctl start $SERVICE_NAME.service # Start timers systemctl start media-cache-builder.timer systemctl start media-embedding-generator.timer systemctl start media-celebrity-enrichment.timer systemctl start plex-match.timer systemctl start media-downloader-db-cleanup.timer echo -e "${GREEN}✓ All services started${NC}" fi # ============================================================================ # OPTIONAL: NGINX REVERSE PROXY # ============================================================================ echo "" if command -v nginx &> /dev/null; then read -p "Configure nginx reverse proxy? (recommended for production) (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo -e "${GREEN}Creating nginx configuration...${NC}" cat > "/etc/nginx/sites-available/media-downloader" << 'NGINX_EOF' # Media Downloader Nginx Configuration # Reverse proxy for API (8000) and Frontend (5173) server { listen 80; server_name _; # Change to your domain # Frontend (Vite) location / { proxy_pass http://127.0.0.1:5173; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } # API Backend location /api/ { proxy_pass http://127.0.0.1:8000/api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket support proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Timeouts for long-running requests proxy_connect_timeout 60s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # WebSocket endpoint location /ws { proxy_pass http://127.0.0.1:8000/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 86400; } # Media files (if serving directly) location /media/ { alias /opt/immich/media/; autoindex off; } # Thumbnails location /thumbnails/ { proxy_pass http://127.0.0.1:8000/api/thumbnails/; proxy_cache_valid 200 1d; } # Increase max upload size for imports client_max_body_size 500M; } NGINX_EOF # Enable site ln -sf /etc/nginx/sites-available/media-downloader /etc/nginx/sites-enabled/ 2>/dev/null || true # Test and reload nginx if nginx -t 2>/dev/null; then systemctl reload nginx echo -e "${GREEN}✓ Nginx configured and reloaded${NC}" else echo -e "${YELLOW}⚠ Nginx config has errors - please check manually${NC}" fi fi else echo -e "${YELLOW}Note: nginx not installed. For production, consider:${NC}" echo " sudo apt install nginx" echo " Then re-run installer or manually configure reverse proxy" fi # ============================================================================ # COMPLETION MESSAGE # ============================================================================ echo "" echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Installation Complete! ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}" echo "" echo -e "${BLUE}Installation location:${NC} $INSTALL_DIR" echo -e "${BLUE}Configuration file:${NC} $INSTALL_DIR/config/settings.json" echo -e "${BLUE}Database directory:${NC} $INSTALL_DIR/database" echo -e "${BLUE}Logs directory:${NC} $INSTALL_DIR/logs" echo "" echo -e "${YELLOW}Services:${NC}" echo " media-downloader - Main scheduler service" echo " media-downloader-api - Web API (port 8000)" echo " media-downloader-frontend - Web UI production (port 5173)" echo " media-downloader-frontend-dev - Web UI development with hot-reload" echo " xvfb-media-downloader - Virtual display for browser automation" echo "" echo -e "${YELLOW}Scheduled Tasks (timers):${NC}" echo " media-cache-builder - Thumbnail cache (daily 3 AM)" echo " media-embedding-generator - CLIP embeddings (daily 3 AM)" echo " media-downloader-db-cleanup- Database cleanup (daily 3 AM)" echo " media-celebrity-enrichment - Celebrity metadata (daily 4 AM)" echo " plex-match - Plex library matching (6 AM, 6 PM)" echo "" echo -e "${YELLOW}Commands:${NC}" echo " media-downloader - Run manual download" echo " media-downloader --scheduler - Run scheduler" echo " media-downloader --scheduler-status - Check scheduler status" echo " media-downloader --platform instagram - Download specific platform" echo "" echo -e "${YELLOW}Service Management:${NC}" echo " sudo systemctl status media-downloader - Check status" echo " sudo systemctl restart media-downloader - Restart scheduler" echo " sudo systemctl restart media-downloader-api- Restart API" echo " sudo journalctl -u media-downloader -f - View logs" echo "" echo -e "${YELLOW}Web Interface:${NC}" echo " API: http://localhost:8000" echo " Frontend: http://localhost:5173" echo "" echo -e "${YELLOW}Development Mode:${NC}" echo " # Switch to development frontend (with hot-reload):" echo " sudo systemctl stop media-downloader-frontend" echo " sudo systemctl start media-downloader-frontend-dev" echo "" echo -e "${YELLOW}To uninstall:${NC}" echo " sudo $INSTALL_DIR/scripts/uninstall.sh"