#!/bin/bash # ============================================================================= # Phase 3 Migration: Mixarr Stack # Target: replicant (.80) # Services: mixarr_mysql, mixarr_redis, mixarr_api, mixarr_web (4 containers) # Run this on the control server (CT 127) # ============================================================================= set -e COMPOSE_DIR=~/clustered-fucks/compose-files/replicant/mixarr PLAYBOOK_DIR=~/clustered-fucks/playbooks echo "========================================" echo "Phase 3: Mixarr Stack Migration" echo "Target: replicant (192.168.1.80)" echo "========================================" echo "" # Create directories mkdir -p "$COMPOSE_DIR" mkdir -p "$PLAYBOOK_DIR" # ============================================================================= # MIXARR DOCKER-COMPOSE # ============================================================================= echo "Creating Mixarr docker-compose.yml..." cat > "$COMPOSE_DIR/docker-compose.yml" << 'EOF' services: # ========================================================================= # MySQL Database # ========================================================================= mysql: image: mysql:8.0 container_name: mixarr_mysql hostname: mysql restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_USER=${MYSQL_USER} - MYSQL_PASSWORD=${MYSQL_PASSWORD} volumes: - ./mysql_data:/var/lib/mysql command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci networks: - mixarr_internal deploy: resources: limits: memory: 1G cpus: '1.0' healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" # ========================================================================= # Redis Cache # ========================================================================= redis: image: redis:7-alpine container_name: mixarr_redis hostname: redis restart: unless-stopped volumes: - ./redis_data:/data command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru networks: - mixarr_internal deploy: resources: limits: memory: 256M cpus: '0.5' healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" # ========================================================================= # Mixarr API # ========================================================================= api: image: ghcr.io/aquantumofdonuts/mixarr:latest container_name: mixarr_api hostname: api restart: unless-stopped ports: - "3005:3005" environment: - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE} - REDIS_URL=redis://redis:6379 - SESSION_SECRET=${SESSION_SECRET} - PORT=3005 - NODE_ENV=production volumes: - ./api_data:/data depends_on: mysql: condition: service_healthy redis: condition: service_healthy command: > sh -c "npx prisma migrate deploy && node apps/api/dist/index.js" networks: - mixarr_internal - proxy deploy: resources: limits: memory: 512M cpus: '1.0' healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:3005/api/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 120s labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" # ========================================================================= # Mixarr Web Frontend # ========================================================================= web: image: ghcr.io/aquantumofdonuts/mixarr:latest container_name: mixarr_web hostname: web restart: unless-stopped ports: - "3006:3000" environment: - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} - SESSION_SECRET=${SESSION_SECRET} - NODE_ENV=production volumes: - ./web_data:/data depends_on: - api networks: - mixarr_internal - proxy deploy: resources: limits: memory: 512M cpus: '1.0' labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" - "homepage.group=Media" - "homepage.name=Mixarr" - "homepage.icon=lidarr.png" - "homepage.href=https://mixarr.3ddbrewery.com" networks: mixarr_internal: driver: bridge proxy: external: true EOF echo "✅ Created $COMPOSE_DIR/docker-compose.yml" # ============================================================================= # ENVIRONMENT FILE TEMPLATE # ============================================================================= echo "Creating .env template..." cat > "$COMPOSE_DIR/.env.example" << 'EOF' # Mixarr Environment Variables # Copy to .env and fill in the secrets # MySQL Database MYSQL_ROOT_PASSWORD=changeme_root_password MYSQL_DATABASE=mixarr MYSQL_USER=mixarr MYSQL_PASSWORD=changeme_mixarr_password # Session Secret (generate with: openssl rand -hex 32) SESSION_SECRET=changeme_session_secret # API URL (used by frontend) NEXT_PUBLIC_API_URL=https://api.mixarr.3ddbrewery.com EOF echo "✅ Created $COMPOSE_DIR/.env.example" # ============================================================================= # ANSIBLE PLAYBOOK # ============================================================================= echo "Creating Ansible playbook..." cat > "$PLAYBOOK_DIR/deploy-mixarr.yml" << 'EOF' --- # Deploy Mixarr Stack to replicant VM # Containers: mysql, redis, api, web # Target: replicant (192.168.1.80) - name: Deploy Mixarr Stack hosts: replicant vars: appdata_path: /home/maddox/docker/appdata service_name: mixarr service_dir: "{{ appdata_path }}/{{ service_name }}" compose_src: "{{ playbook_dir }}/../compose-files/replicant/{{ service_name }}" tasks: # ========================================================================= # PRE-FLIGHT CHECKS # ========================================================================= - name: Check if .env file exists on control server delegate_to: localhost ansible.builtin.stat: path: "{{ compose_src }}/.env" register: env_file - name: Fail if .env is missing ansible.builtin.fail: msg: | .env file not found at {{ compose_src }}/.env Copy .env.example to .env and fill in the secrets! when: not env_file.stat.exists # ========================================================================= # DIRECTORY SETUP # ========================================================================= - name: Create mixarr base directory ansible.builtin.file: path: "{{ service_dir }}" state: directory owner: maddox group: maddox mode: '0755' - name: Create mysql_data directory ansible.builtin.file: path: "{{ service_dir }}/mysql_data" state: directory owner: maddox group: maddox mode: '0755' - name: Create redis_data directory ansible.builtin.file: path: "{{ service_dir }}/redis_data" state: directory owner: maddox group: maddox mode: '0755' - name: Create api_data directory ansible.builtin.file: path: "{{ service_dir }}/api_data" state: directory owner: maddox group: maddox mode: '0755' - name: Create web_data directory ansible.builtin.file: path: "{{ service_dir }}/web_data" state: directory owner: maddox group: maddox mode: '0755' # ========================================================================= # FILE DEPLOYMENT # ========================================================================= - name: Copy docker-compose.yml ansible.builtin.copy: src: "{{ compose_src }}/docker-compose.yml" dest: "{{ service_dir }}/docker-compose.yml" owner: maddox group: maddox mode: '0644' - name: Copy .env file ansible.builtin.copy: src: "{{ compose_src }}/.env" dest: "{{ service_dir }}/.env" owner: maddox group: maddox mode: '0600' # ========================================================================= # CONTAINER DEPLOYMENT # ========================================================================= - name: Deploy mixarr stack community.docker.docker_compose_v2: project_src: "{{ service_dir }}" state: present pull: always register: mixarr_result - name: Show deployment status ansible.builtin.debug: msg: "Mixarr stack deployed: {{ mixarr_result.changed }}" # ========================================================================= # VERIFICATION # ========================================================================= - name: Wait for MySQL to be ready ansible.builtin.command: cmd: docker exec mixarr_mysql mysqladmin ping -h localhost register: mysql_check retries: 30 delay: 5 until: mysql_check.rc == 0 changed_when: false - name: Wait for Redis to be ready ansible.builtin.command: cmd: docker exec mixarr_redis redis-cli ping register: redis_check retries: 10 delay: 3 until: "'PONG' in redis_check.stdout" changed_when: false - name: Wait for API to be healthy (may take up to 2 minutes for Prisma migrations) ansible.builtin.uri: url: "http://localhost:3005/api/health" status_code: 200 timeout: 10 register: api_health retries: 30 delay: 10 until: api_health.status == 200 ignore_errors: true - name: Wait for Web frontend to be ready ansible.builtin.uri: url: "http://localhost:3006" status_code: [200, 302] timeout: 10 register: web_health retries: 15 delay: 5 until: web_health.status in [200, 302] ignore_errors: true - name: Summary ansible.builtin.debug: msg: - "=========================================" - "Mixarr Stack Deployment Complete" - "=========================================" - "✅ MySQL: Running on internal network" - "✅ Redis: Running on internal network" - "✅ API: http://192.168.1.80:3005" - "✅ Web: http://192.168.1.80:3006" - "=========================================" EOF echo "✅ Created $PLAYBOOK_DIR/deploy-mixarr.yml" echo "" echo "========================================" echo "FILES CREATED" echo "========================================" echo " $COMPOSE_DIR/docker-compose.yml" echo " $COMPOSE_DIR/.env.example" echo " $PLAYBOOK_DIR/deploy-mixarr.yml" echo "" echo "========================================" echo "NEXT STEPS" echo "========================================" echo "" echo "1. GET SECRETS from alien (check .env or compose file):" echo " ssh alien 'cat /mnt/docker-storage/appdata/mixarr/.env || cat /mnt/docker-storage/appdata/mixarr/docker-compose.yml'" echo "" echo " Look for:" echo " - MYSQL_ROOT_PASSWORD" echo " - MYSQL_PASSWORD" echo " - SESSION_SECRET" echo "" echo "2. CREATE .env file with secrets:" echo " cp $COMPOSE_DIR/.env.example $COMPOSE_DIR/.env" echo " nano $COMPOSE_DIR/.env" echo "" echo "3. STOP OLD CONTAINERS on alien:" echo " ssh alien 'cd /mnt/docker-storage/appdata/mixarr && docker compose down'" echo "" echo "4. CREATE TARGET DIRECTORIES on replicant:" echo " ssh replicant 'mkdir -p /home/maddox/docker/appdata/mixarr/{mysql_data,redis_data,api_data,web_data}'" echo "" echo "5. EXPORT MySQL DATA (if preserving existing data):" echo " ssh alien 'docker exec mixarr_mysql mysqldump -u root -pROOT_PASSWORD mixarr > /tmp/mixarr_backup.sql'" echo " scp alien:/tmp/mixarr_backup.sql /tmp/" echo "" echo "6. DEPLOY to replicant:" echo " ansible-playbook playbooks/deploy-mixarr.yml" echo "" echo "7. IMPORT MySQL DATA (if applicable):" echo " scp /tmp/mixarr_backup.sql replicant:/tmp/" echo " ssh replicant 'docker exec -i mixarr_mysql mysql -u root -pROOT_PASSWORD mixarr < /tmp/mixarr_backup.sql'" echo "" echo "8. VERIFY services:" echo " curl http://192.168.1.80:3005/api/health # API" echo " curl -I http://192.168.1.80:3006 # Web frontend" echo "" echo "9. UPDATE TRAEFIK config:" echo " Change mixarr API backend from alien to replicant (192.168.1.80:3005)" echo " Change mixarr Web backend from alien to replicant (192.168.1.80:3006)" echo "" echo "10. CLEANUP alien:" echo " ssh alien 'cd /mnt/docker-storage/appdata/mixarr && docker compose rm -f'" echo "" echo "11. COMMIT changes:" echo " cd ~/clustered-fucks" echo " git add -A" echo " git commit -m 'Phase 3: Deploy mixarr stack to replicant'" echo " git push" echo "" echo "========================================" echo "IMPORTANT NOTES" echo "========================================" echo "- Mixarr uses MySQL - DATABASE STATE MATTERS" echo " - Export/import database if you have existing data" echo " - Or start fresh with Prisma migrations" echo "" echo "- Original location on alien: /mnt/docker-storage/appdata/mixarr" echo " (Not the usual /home/maddox/docker/appdata path)" echo "" echo "- API has 2-minute startup period for Prisma migrations" echo " - First deploy may be slow, subsequent restarts faster" echo "" echo "- Docker volumes are replaced with bind mounts for easier backup/migration" echo ""