diff --git a/configs/mixarr.yml b/configs/mixarr.yml new file mode 100644 index 0000000..d1f4366 --- /dev/null +++ b/configs/mixarr.yml @@ -0,0 +1,48 @@ +http: + routers: + # Mixarr Web Frontend - with auth + mixarr-web: + entryPoints: + - web-secure + tls: + certResolver: default + service: mixarr-web + rule: (Host(`mixarr.fails.me`) || Host(`mixarr.3ddbrewery.com`)) && !PathPrefix(`/api`) + middlewares: + - secure-headers + - authentik + + # Mixarr Web API paths - no auth (app handles session auth) + mixarr-web-api: + entryPoints: + - web-secure + tls: + certResolver: default + service: mixarr-web + rule: (Host(`mixarr.fails.me`) || Host(`mixarr.3ddbrewery.com`)) && PathPrefix(`/api`) + middlewares: + - secure-headers + + # Mixarr Backend API - no auth (called by frontend) + mixarr-api: + entryPoints: + - web-secure + tls: + certResolver: default + service: mixarr-api + rule: Host(`api.mixarr.fails.me`) || Host(`api.mixarr.3ddbrewery.com`) + middlewares: + - secure-headers + + services: + mixarr-web: + loadBalancer: + servers: + - url: http://192.168.1.80:3006 + passHostHeader: false + + mixarr-api: + loadBalancer: + servers: + - url: http://192.168.1.80:3005 + passHostHeader: false diff --git a/migration/phase3-db-admin-tools.sh b/migration/phase3-db-admin-tools.sh new file mode 100755 index 0000000..f3d7125 --- /dev/null +++ b/migration/phase3-db-admin-tools.sh @@ -0,0 +1,275 @@ +#!/bin/bash +# ============================================================================= +# Phase 3 Migration: Database Admin Tools +# Target: databases (.81) +# Services: phpmyadmin, phppgadmin +# Run this on the control server (CT 127) +# ============================================================================= + +set -e + +COMPOSE_DIR_PMA=~/clustered-fucks/compose-files/databases/phpmyadmin +COMPOSE_DIR_PGA=~/clustered-fucks/compose-files/databases/phppgadmin +PLAYBOOK_DIR=~/clustered-fucks/playbooks + +echo "========================================" +echo "Phase 3: Database Admin Tools Migration" +echo "Target: databases (192.168.1.81)" +echo "========================================" +echo "" + +# Create directories +mkdir -p "$COMPOSE_DIR_PMA" +mkdir -p "$COMPOSE_DIR_PGA" +mkdir -p "$PLAYBOOK_DIR" + +# ============================================================================= +# PHPMYADMIN +# ============================================================================= + +echo "Creating phpMyAdmin compose file..." + +cat > "$COMPOSE_DIR_PMA/docker-compose.yml" << 'EOF' +services: + phpmyadmin: + image: phpmyadmin:latest + container_name: phpmyadmin + hostname: phpmyadmin + environment: + # Multiple MySQL hosts: Hetzner (im), NAS (different ports) + - PMA_HOSTS=192.168.12.3,192.168.1.251,192.168.1.251 + - PMA_PORTS=3306,33306,3306 + - MAX_EXECUTION_TIME=300 + - MEMORY_LIMIT=512M + - UPLOAD_LIMIT=2048K + - TZ=America/Indiana/Indianapolis + ports: + - "2500:80" + restart: unless-stopped + networks: + - proxy + deploy: + resources: + limits: + memory: 512M + cpus: '1.0' + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + - "homepage.group=Infrastructure" + - "homepage.name=Phpmyadmin" + - "homepage.icon=phpmyadmin.png" + - "homepage.href=https://php.3ddbrewery.com" + +networks: + proxy: + external: true +EOF + +echo "✅ Created $COMPOSE_DIR_PMA/docker-compose.yml" + +# ============================================================================= +# PHPPGADMIN +# ============================================================================= + +echo "Creating phpPgAdmin compose file..." + +cat > "$COMPOSE_DIR_PGA/docker-compose.yml" << 'EOF' +services: + phppgadmin: + image: dockage/phppgadmin:latest + container_name: phppgadmin + hostname: phppgadmin + environment: + # PostgreSQL on Hetzner (im) + - PHP_PG_ADMIN_SERVER_HOST=192.168.12.2 + - PHP_PG_ADMIN_SERVER_PORT=55432 + - PHP_PG_ADMIN_SERVER_SSL_MODE=allow + ports: + - "5183:80" + - "4433:443" + restart: unless-stopped + volumes: + - ./data:/data + - ./logs:/var/log + networks: + - proxy + deploy: + resources: + limits: + memory: 256M + cpus: '0.5' + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + - "homepage.group=Infrastructure" + - "homepage.name=PhpPGadmin" + - "homepage.icon=postgres.png" + - "homepage.href=https://phppgadmin.3ddbrewery.com" + +networks: + proxy: + external: true +EOF + +echo "✅ Created $COMPOSE_DIR_PGA/docker-compose.yml" + +# ============================================================================= +# ANSIBLE PLAYBOOK - Combined Deployment +# ============================================================================= + +echo "Creating combined Ansible playbook..." + +cat > "$PLAYBOOK_DIR/deploy-db-admin-tools.yml" << 'EOF' +--- +# Deploy Database Admin Tools to databases VM +# Deploys: phpmyadmin, phppgadmin +# Target: databases (192.168.1.81) + +- name: Deploy Database Admin Tools + hosts: databases + vars: + appdata_path: /home/docker/appdata + compose_src: "{{ playbook_dir }}/../compose-files/databases" + + tasks: + # ========================================================================= + # PHPMYADMIN + # ========================================================================= + - name: Create phpmyadmin directory + ansible.builtin.file: + path: "{{ appdata_path }}/phpmyadmin" + state: directory + mode: '0755' + + - name: Copy phpmyadmin docker-compose.yml + ansible.builtin.copy: + src: "{{ compose_src }}/phpmyadmin/docker-compose.yml" + dest: "{{ appdata_path }}/phpmyadmin/docker-compose.yml" + mode: '0644' + + - name: Deploy phpmyadmin container + community.docker.docker_compose_v2: + project_src: "{{ appdata_path }}/phpmyadmin" + state: present + pull: always + register: phpmyadmin_result + + - name: Show phpmyadmin status + ansible.builtin.debug: + msg: "phpMyAdmin deployed: {{ phpmyadmin_result.changed }}" + + # ========================================================================= + # PHPPGADMIN + # ========================================================================= + - name: Create phppgadmin directory + ansible.builtin.file: + path: "{{ appdata_path }}/phppgadmin" + state: directory + mode: '0755' + + - name: Create phppgadmin data directory + ansible.builtin.file: + path: "{{ appdata_path }}/phppgadmin/data" + state: directory + mode: '0755' + + - name: Create phppgadmin logs directory + ansible.builtin.file: + path: "{{ appdata_path }}/phppgadmin/logs" + state: directory + mode: '0755' + + - name: Copy phppgadmin docker-compose.yml + ansible.builtin.copy: + src: "{{ compose_src }}/phppgadmin/docker-compose.yml" + dest: "{{ appdata_path }}/phppgadmin/docker-compose.yml" + mode: '0644' + + - name: Deploy phppgadmin container + community.docker.docker_compose_v2: + project_src: "{{ appdata_path }}/phppgadmin" + state: present + pull: always + register: phppgadmin_result + + - name: Show phppgadmin status + ansible.builtin.debug: + msg: "phpPgAdmin deployed: {{ phppgadmin_result.changed }}" + + # ========================================================================= + # VERIFICATION + # ========================================================================= + - name: Wait for phpmyadmin to be ready + ansible.builtin.uri: + url: "http://localhost:2500" + status_code: 200 + timeout: 5 + register: pma_health + retries: 10 + delay: 5 + until: pma_health.status == 200 + + - name: Wait for phppgadmin to be ready + ansible.builtin.uri: + url: "http://localhost:5183" + status_code: [200, 302] + timeout: 5 + register: pga_health + retries: 10 + delay: 5 + until: pga_health.status in [200, 302] + + - name: Summary + ansible.builtin.debug: + msg: + - "✅ phpMyAdmin: http://192.168.1.81:2500" + - "✅ phpPgAdmin: http://192.168.1.81:5183" +EOF + +echo "✅ Created $PLAYBOOK_DIR/deploy-db-admin-tools.yml" + +echo "" +echo "========================================" +echo "FILES CREATED" +echo "========================================" +echo " $COMPOSE_DIR_PMA/docker-compose.yml" +echo " $COMPOSE_DIR_PGA/docker-compose.yml" +echo " $PLAYBOOK_DIR/deploy-db-admin-tools.yml" +echo "" +echo "========================================" +echo "NEXT STEPS" +echo "========================================" +echo "" +echo "1. STOP OLD CONTAINERS on alien:" +echo " ssh alien 'docker stop phpmyadmin phppgadmin-phppgadmin-1'" +echo "" +echo "2. DEPLOY to databases:" +echo " ansible-playbook playbooks/deploy-db-admin-tools.yml" +echo "" +echo "3. VERIFY services:" +echo " curl -I http://192.168.1.81:2500 # phpMyAdmin" +echo " curl -I http://192.168.1.81:5183 # phpPgAdmin" +echo "" +echo "4. UPDATE TRAEFIK config on Hetzner:" +echo " Change backend IPs from alien (192.168.1.252) to databases (192.168.1.81)" +echo " - phpmyadmin: port 2500" +echo " - phppgadmin: port 5183" +echo "" +echo "5. CLEANUP alien:" +echo " ssh alien 'docker rm phpmyadmin phppgadmin-phppgadmin-1'" +echo "" +echo "6. COMMIT changes:" +echo " cd ~/clustered-fucks" +echo " git add -A" +echo " git commit -m 'Phase 3: Deploy phpmyadmin + phppgadmin to databases'" +echo " git push" +echo "" +echo "========================================" +echo "NOTES" +echo "========================================" +echo "- Both services are STATELESS (phpmyadmin has no mounts)" +echo "- phppgadmin had anonymous Docker volumes on alien (not important)" +echo "- phpMyAdmin connects to MySQL on: Hetzner (192.168.12.3:3306), NAS (192.168.1.251:33306 & :3306)" +echo "- phpPgAdmin connects to PostgreSQL on: Hetzner (192.168.12.2:55432)" +echo "" diff --git a/migration/phase3-mixarr.sh b/migration/phase3-mixarr.sh new file mode 100755 index 0000000..db36a41 --- /dev/null +++ b/migration/phase3-mixarr.sh @@ -0,0 +1,445 @@ +#!/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 ""