#!/bin/bash # ============================================================================= # Phase 3 Migration: Karakeep Stack # Target: databases (.81) # Services: karakeep-web, meilisearch, chrome, ollama (4 containers) # Run this on the control server (CT 127) # ============================================================================= set -e COMPOSE_DIR=~/clustered-fucks/compose-files/databases/karakeep PLAYBOOK_DIR=~/clustered-fucks/playbooks echo "========================================" echo "Phase 3: Karakeep Stack Migration" echo "Target: databases (192.168.1.81)" echo "========================================" echo "" # Create directories mkdir -p "$COMPOSE_DIR" mkdir -p "$PLAYBOOK_DIR" # ============================================================================= # KARAKEEP DOCKER-COMPOSE # ============================================================================= echo "Creating Karakeep docker-compose.yml..." cat > "$COMPOSE_DIR/docker-compose.yml" << 'EOF' services: # ========================================================================= # Karakeep Web - Main application # ========================================================================= web: image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION:-release} container_name: karakeep-web hostname: karakeep-web restart: unless-stopped volumes: - ./data:/data ports: - "3054:3000" environment: - DATA_DIR=/data - MEILI_ADDR=http://meilisearch:7700 - MEILI_MASTER_KEY=${MEILI_MASTER_KEY} - BROWSER_WEB_URL=http://chrome:9222 - OLLAMA_BASE_URL=http://ollama:11434 - INFERENCE_TEXT_MODEL=${INFERENCE_TEXT_MODEL:-llama3.2:3b} - INFERENCE_IMAGE_MODEL=${INFERENCE_IMAGE_MODEL:-llava} - INFERENCE_LANG=${INFERENCE_LANG:-english} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - NODE_ENV=production - NEXT_TELEMETRY_DISABLED=1 depends_on: meilisearch: condition: service_started chrome: condition: service_started ollama: condition: service_started networks: - karakeep_internal - proxy deploy: resources: limits: memory: 1G cpus: '2.0' healthcheck: test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 30s labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" - "homepage.group=Personal" - "homepage.name=Karakeep" - "homepage.icon=karakeep.png" - "homepage.href=https://karakeep.3ddbrewery.com" # ========================================================================= # Meilisearch - Full-text search engine # ========================================================================= meilisearch: image: getmeili/meilisearch:v1.13.3 container_name: karakeep-meilisearch hostname: meilisearch restart: unless-stopped volumes: - ./meilisearch:/meili_data ports: - "7700:7700" environment: - MEILI_NO_ANALYTICS=true - MEILI_MASTER_KEY=${MEILI_MASTER_KEY} networks: - karakeep_internal deploy: resources: limits: memory: 1G cpus: '1.0' labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" # ========================================================================= # Chrome - Headless browser for screenshots/crawling # ========================================================================= chrome: image: gcr.io/zenika-hub/alpine-chrome:123 container_name: karakeep-chrome hostname: chrome restart: unless-stopped command: - --no-sandbox - --disable-gpu - --disable-dev-shm-usage - --remote-debugging-address=0.0.0.0 - --remote-debugging-port=9222 - --hide-scrollbars networks: - karakeep_internal deploy: resources: limits: memory: 512M cpus: '1.0' labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" # ========================================================================= # Ollama - Local AI inference (CPU-only on databases VM) # ========================================================================= ollama: image: ollama/ollama:latest container_name: karakeep-ollama hostname: ollama restart: unless-stopped volumes: - ./ollama:/root/.ollama ports: - "11434:11434" environment: - OLLAMA_HOST=0.0.0.0:11434 networks: - karakeep_internal deploy: resources: limits: # Ollama needs more memory for models memory: 4G cpus: '4.0' labels: - "autoheal=true" - "com.centurylinklabs.watchtower.enable=true" networks: karakeep_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' # Karakeep Environment Variables # Copy to .env and fill in the secrets # Version KARAKEEP_VERSION=release # Meilisearch MEILI_MASTER_KEY=your-meilisearch-master-key-here # NextAuth NEXTAUTH_URL=https://karakeep.3ddbrewery.com NEXTAUTH_SECRET=your-nextauth-secret-here # AI Models INFERENCE_TEXT_MODEL=llama3.2:3b INFERENCE_IMAGE_MODEL=llava INFERENCE_LANG=english EOF echo "✅ Created $COMPOSE_DIR/.env.example" # ============================================================================= # ANSIBLE PLAYBOOK # ============================================================================= echo "Creating Ansible playbook..." cat > "$PLAYBOOK_DIR/deploy-karakeep.yml" << 'EOF' --- # Deploy Karakeep Stack to databases VM # Containers: web, meilisearch, chrome, ollama # Target: databases (192.168.1.81) - name: Deploy Karakeep Stack hosts: databases vars: appdata_path: /home/docker/appdata service_name: karakeep service_dir: "{{ appdata_path }}/{{ service_name }}" compose_src: "{{ playbook_dir }}/../compose-files/databases/{{ 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 karakeep base directory ansible.builtin.file: path: "{{ service_dir }}" state: directory mode: '0755' - name: Create karakeep data directory ansible.builtin.file: path: "{{ service_dir }}/data" state: directory mode: '0755' - name: Create meilisearch directory ansible.builtin.file: path: "{{ service_dir }}/meilisearch" state: directory mode: '0755' - name: Create ollama directory ansible.builtin.file: path: "{{ service_dir }}/ollama" state: directory mode: '0755' # ========================================================================= # FILE DEPLOYMENT # ========================================================================= - name: Copy docker-compose.yml ansible.builtin.copy: src: "{{ compose_src }}/docker-compose.yml" dest: "{{ service_dir }}/docker-compose.yml" mode: '0644' - name: Copy .env file ansible.builtin.copy: src: "{{ compose_src }}/.env" dest: "{{ service_dir }}/.env" mode: '0600' # ========================================================================= # CONTAINER DEPLOYMENT # ========================================================================= - name: Deploy karakeep stack community.docker.docker_compose_v2: project_src: "{{ service_dir }}" state: present pull: always register: karakeep_result - name: Show deployment status ansible.builtin.debug: msg: "Karakeep stack deployed: {{ karakeep_result.changed }}" # ========================================================================= # VERIFICATION # ========================================================================= - name: Wait for karakeep-web to be healthy ansible.builtin.uri: url: "http://localhost:3054/api/health" status_code: 200 timeout: 10 register: web_health retries: 15 delay: 10 until: web_health.status == 200 ignore_errors: true - name: Wait for meilisearch to be ready ansible.builtin.uri: url: "http://localhost:7700/health" status_code: 200 timeout: 5 register: meili_health retries: 10 delay: 5 until: meili_health.status == 200 ignore_errors: true - name: Wait for ollama to be ready ansible.builtin.uri: url: "http://localhost:11434/api/version" status_code: 200 timeout: 5 register: ollama_health retries: 10 delay: 5 until: ollama_health.status == 200 ignore_errors: true - name: Summary ansible.builtin.debug: msg: - "=========================================" - "Karakeep Stack Deployment Complete" - "=========================================" - "✅ Karakeep Web: http://192.168.1.81:3054" - "✅ Meilisearch: http://192.168.1.81:7700" - "✅ Ollama: http://192.168.1.81:11434" - "=========================================" - "NOTE: Ollama running on CPU only (no GPU)" - "Models may need to be re-pulled after migration" EOF echo "✅ Created $PLAYBOOK_DIR/deploy-karakeep.yml" echo "" echo "========================================" echo "FILES CREATED" echo "========================================" echo " $COMPOSE_DIR/docker-compose.yml" echo " $COMPOSE_DIR/.env.example" echo " $PLAYBOOK_DIR/deploy-karakeep.yml" echo "" echo "========================================" echo "NEXT STEPS" echo "========================================" echo "" echo "1. GET SECRETS from alien .env file:" echo " ssh alien 'cat /home/maddox/docker/appdata/karakeep/.env'" 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 /home/maddox/docker/appdata/karakeep && docker compose down'" echo "" echo "4. CREATE TARGET DIRECTORIES on databases:" echo " ssh databases 'mkdir -p /home/docker/appdata/karakeep/{data,meilisearch,ollama}'" echo "" echo "5. RSYNC DATA (run FROM databases):" echo " ssh databases" echo " # Main karakeep data" echo " rsync -avP maddox@alien:/home/maddox/docker/appdata/karakeep/ /home/docker/appdata/karakeep/" echo "" echo " If permission denied, first run on alien:" echo " ssh alien 'sudo chown -R maddox:maddox /home/maddox/docker/appdata/karakeep/'" echo "" echo "6. DEPLOY to databases:" echo " ansible-playbook playbooks/deploy-karakeep.yml" echo "" echo "7. VERIFY services:" echo " curl http://192.168.1.81:3054/api/health # Karakeep" echo " curl http://192.168.1.81:7700/health # Meilisearch" echo " curl http://192.168.1.81:11434/api/version # Ollama" echo "" echo "8. UPDATE TRAEFIK config:" echo " Change karakeep backend IP from alien to databases (192.168.1.81:3054)" echo "" echo "9. CLEANUP alien:" echo " ssh alien 'cd /home/maddox/docker/appdata/karakeep && docker compose rm -f'" echo "" echo "10. COMMIT changes:" echo " cd ~/clustered-fucks" echo " git add -A" echo " git commit -m 'Phase 3: Deploy karakeep stack to databases'" echo " git push" echo "" echo "========================================" echo "IMPORTANT NOTES" echo "========================================" echo "- Karakeep has stateful data in:" echo " - /data (bookmarks, assets)" echo " - /meilisearch (search index)" echo " - /ollama (AI models)" echo "" echo "- Ollama on databases runs CPU-ONLY (no GPU)" echo " - AI tagging will be slower than on alien" echo " - Models (llama3.2:3b, llava) need to be present" echo "" echo "- Consider moving ollama to pve-alien later for GPU acceleration" echo ""