scripts/migration/phase3-mixarr.sh
Maddox 9a97beeaf9 Add mixarr traefik config and migration scripts
- configs/mixarr.yml: Traefik routing for mixarr web + API
- migration/phase3-mixarr.sh: Migration script for mixarr stack
- migration/phase3-db-admin-tools.sh: Migration script for db admin tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:10:56 +00:00

445 lines
14 KiB
Bash
Executable file

#!/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 ""