diff --git a/CLAUDE.md b/CLAUDE.md index cdc35a0..f7d1dc1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,3 +91,9 @@ ansible-playbook playbooks/deploy-.yml - `inventory/hosts.yml` - All managed hosts with IPs and connection settings - `inventory/group_vars/all.yml` - Global variables (timezone, NAS paths, ntfy topics) - `docs/control-server-guide.md` - Detailed operations guide + +## Additional Context Paths + +The following external directories are part of this project's context: + +- `~/scripts/` - Utility scripts for homelab management (add-host.sh, control-menu.sh, ssh-manager.sh, sync-dyno.sh) diff --git a/compose-files/replicant/audiobookshelf/docker-compose.yml b/compose-files/replicant/audiobookshelf/docker-compose.yml new file mode 100644 index 0000000..5bcce22 --- /dev/null +++ b/compose-files/replicant/audiobookshelf/docker-compose.yml @@ -0,0 +1,25 @@ +services: + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + restart: unless-stopped + ports: + - "13378:80" + volumes: + - ./config:/config + - ./metadata:/metadata + - /mnt/nas/media/audiobooks:/audiobooks + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 1G + cpus: "1.0" + +networks: + proxy: + external: true diff --git a/compose-files/replicant/calibre/docker-compose.yml b/compose-files/replicant/calibre/docker-compose.yml new file mode 100644 index 0000000..7ffb7cd --- /dev/null +++ b/compose-files/replicant/calibre/docker-compose.yml @@ -0,0 +1,66 @@ +services: + calibre-server: + image: linuxserver/calibre:latest + container_name: calibre-server + restart: unless-stopped + security_opt: + - seccomp:unconfined + ports: + - "28080:8080" + - "28081:8081" + - "28181:8181" + volumes: + - ./config:/config + - /mnt/nas/media/Books:/books + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Indiana/Indianapolis + - GUAC_USER=calibre + - GUAC_PASS=calibre + - CALIBRE_SERVERSIDE_BROWSE=1 + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 1G + cpus: "1.0" + + calibre-web: + image: linuxserver/calibre-web:latest + container_name: calibre-web + restart: unless-stopped + security_opt: + - seccomp:unconfined + ports: + - "28083:8083" + volumes: + - ./config:/config + - /mnt/nas/media/Books:/books:ro + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Indiana/Indianapolis + - DOCKER_MODS=linuxserver/mods:calibre-web-calibre + - CALIBRE_DBPATH=/books + - OAUTHLIB_RELAX_TOKEN_SCOPE=1 + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 512M + cpus: "0.5" + depends_on: + - calibre-server + +networks: + proxy: + external: true diff --git a/playbooks/deploy-audiobookshelf.yml b/playbooks/deploy-audiobookshelf.yml new file mode 100644 index 0000000..751dc81 --- /dev/null +++ b/playbooks/deploy-audiobookshelf.yml @@ -0,0 +1,56 @@ +--- +- name: Deploy Audiobookshelf + hosts: replicant + become: true + vars: + service_dir: /home/maddox/docker/appdata/audiobookshelf + + tasks: + - name: Create audiobookshelf directory + ansible.builtin.file: + path: "{{ service_dir }}" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create config subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/config" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create metadata subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/metadata" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Copy docker-compose.yml + ansible.builtin.copy: + src: ../compose-files/replicant/audiobookshelf/docker-compose.yml + dest: "{{ service_dir }}/docker-compose.yml" + owner: maddox + group: maddox + mode: "0644" + + - name: Ensure proxy network exists + community.docker.docker_network: + name: proxy + state: present + + - name: Deploy audiobookshelf + community.docker.docker_compose_v2: + project_src: "{{ service_dir }}" + state: present + pull: always + register: compose_result + + - name: Show deployment result + ansible.builtin.debug: + msg: "Audiobookshelf deployed successfully" + when: compose_result.changed diff --git a/playbooks/deploy-calibre.yml b/playbooks/deploy-calibre.yml new file mode 100644 index 0000000..dbd3efc --- /dev/null +++ b/playbooks/deploy-calibre.yml @@ -0,0 +1,48 @@ +--- +- name: Deploy Calibre Stack (calibre-server + calibre-web) + hosts: replicant + become: true + vars: + service_dir: /home/maddox/docker/appdata/calibre + + tasks: + - name: Create calibre directory + ansible.builtin.file: + path: "{{ service_dir }}" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create config subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/config" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Copy docker-compose.yml + ansible.builtin.copy: + src: ../compose-files/replicant/calibre/docker-compose.yml + dest: "{{ service_dir }}/docker-compose.yml" + owner: maddox + group: maddox + mode: "0644" + + - name: Ensure proxy network exists + community.docker.docker_network: + name: proxy + state: present + + - name: Deploy calibre stack + community.docker.docker_compose_v2: + project_src: "{{ service_dir }}" + state: present + pull: always + register: compose_result + + - name: Show deployment result + ansible.builtin.debug: + msg: "Calibre stack deployed successfully" + when: compose_result.changed diff --git a/scripts/migrate-phase2-books.sh b/scripts/migrate-phase2-books.sh new file mode 100755 index 0000000..ae85fdd --- /dev/null +++ b/scripts/migrate-phase2-books.sh @@ -0,0 +1,287 @@ +#!/bin/bash +# Phase 2 Migration: Calibre Stack + Audiobookshelf +# Target: replicant (.80) +# Run from control server (CT 127) + +set -e + +REPO_DIR="$HOME/clustered-fucks" +COMPOSE_DIR="$REPO_DIR/compose-files/replicant" +PLAYBOOK_DIR="$REPO_DIR/playbooks" + +echo "=== Phase 2: Calibre Stack + Audiobookshelf Migration ===" +echo "" + +# Create directories +mkdir -p "$COMPOSE_DIR/calibre" +mkdir -p "$COMPOSE_DIR/audiobookshelf" + +# ============================================================================= +# Calibre Stack (calibre-server + calibre-web) +# ============================================================================= +cat > "$COMPOSE_DIR/calibre/docker-compose.yml" << 'EOF' +services: + calibre-server: + image: linuxserver/calibre:latest + container_name: calibre-server + restart: unless-stopped + security_opt: + - seccomp:unconfined + ports: + - "28080:8080" + - "28081:8081" + - "28181:8181" + volumes: + - ./config:/config + - /mnt/nas/media/Books:/books + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Indiana/Indianapolis + - GUAC_USER=calibre + - GUAC_PASS=calibre + - CALIBRE_SERVERSIDE_BROWSE=1 + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 1G + cpus: "1.0" + + calibre-web: + image: linuxserver/calibre-web:latest + container_name: calibre-web + restart: unless-stopped + security_opt: + - seccomp:unconfined + ports: + - "28083:8083" + volumes: + - ./config:/config + - /mnt/nas/media/Books:/books:ro + environment: + - PUID=1000 + - PGID=1000 + - TZ=America/Indiana/Indianapolis + - DOCKER_MODS=linuxserver/mods:calibre-web-calibre + - CALIBRE_DBPATH=/books + - OAUTHLIB_RELAX_TOKEN_SCOPE=1 + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 512M + cpus: "0.5" + depends_on: + - calibre-server + +networks: + proxy: + external: true +EOF + +echo "[OK] Created $COMPOSE_DIR/calibre/docker-compose.yml" + +# ============================================================================= +# Audiobookshelf +# ============================================================================= +cat > "$COMPOSE_DIR/audiobookshelf/docker-compose.yml" << 'EOF' +services: + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + restart: unless-stopped + ports: + - "13378:80" + volumes: + - ./config:/config + - ./metadata:/metadata + - /mnt/nas/media/audiobooks:/audiobooks + networks: + - proxy + labels: + - "autoheal=true" + - "com.centurylinklabs.watchtower.enable=true" + deploy: + resources: + limits: + memory: 1G + cpus: "1.0" + +networks: + proxy: + external: true +EOF + +echo "[OK] Created $COMPOSE_DIR/audiobookshelf/docker-compose.yml" + +# ============================================================================= +# Ansible Playbook: deploy-calibre.yml +# ============================================================================= +cat > "$PLAYBOOK_DIR/deploy-calibre.yml" << 'EOF' +--- +- name: Deploy Calibre Stack (calibre-server + calibre-web) + hosts: replicant + become: true + vars: + service_dir: /home/maddox/docker/appdata/calibre + + tasks: + - name: Create calibre directory + ansible.builtin.file: + path: "{{ service_dir }}" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create config subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/config" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Copy docker-compose.yml + ansible.builtin.copy: + src: ../compose-files/replicant/calibre/docker-compose.yml + dest: "{{ service_dir }}/docker-compose.yml" + owner: maddox + group: maddox + mode: "0644" + + - name: Ensure proxy network exists + community.docker.docker_network: + name: proxy + state: present + + - name: Deploy calibre stack + community.docker.docker_compose_v2: + project_src: "{{ service_dir }}" + state: present + pull: always + register: compose_result + + - name: Show deployment result + ansible.builtin.debug: + msg: "Calibre stack deployed successfully" + when: compose_result.changed +EOF + +echo "[OK] Created $PLAYBOOK_DIR/deploy-calibre.yml" + +# ============================================================================= +# Ansible Playbook: deploy-audiobookshelf.yml +# ============================================================================= +cat > "$PLAYBOOK_DIR/deploy-audiobookshelf.yml" << 'EOF' +--- +- name: Deploy Audiobookshelf + hosts: replicant + become: true + vars: + service_dir: /home/maddox/docker/appdata/audiobookshelf + + tasks: + - name: Create audiobookshelf directory + ansible.builtin.file: + path: "{{ service_dir }}" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create config subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/config" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Create metadata subdirectory + ansible.builtin.file: + path: "{{ service_dir }}/metadata" + state: directory + owner: maddox + group: maddox + mode: "0755" + + - name: Copy docker-compose.yml + ansible.builtin.copy: + src: ../compose-files/replicant/audiobookshelf/docker-compose.yml + dest: "{{ service_dir }}/docker-compose.yml" + owner: maddox + group: maddox + mode: "0644" + + - name: Ensure proxy network exists + community.docker.docker_network: + name: proxy + state: present + + - name: Deploy audiobookshelf + community.docker.docker_compose_v2: + project_src: "{{ service_dir }}" + state: present + pull: always + register: compose_result + + - name: Show deployment result + ansible.builtin.debug: + msg: "Audiobookshelf deployed successfully" + when: compose_result.changed +EOF + +echo "[OK] Created $PLAYBOOK_DIR/deploy-audiobookshelf.yml" + +# ============================================================================= +# Summary +# ============================================================================= +echo "" +echo "=== Files Created ===" +echo " Compose files:" +echo " - $COMPOSE_DIR/calibre/docker-compose.yml" +echo " - $COMPOSE_DIR/audiobookshelf/docker-compose.yml" +echo " Playbooks:" +echo " - $PLAYBOOK_DIR/deploy-calibre.yml" +echo " - $PLAYBOOK_DIR/deploy-audiobookshelf.yml" +echo "" +echo "=== Migration Steps ===" +echo "" +echo "1. Stop containers on alien:" +echo " ssh alien 'docker stop calibre-server calibre-web audiobookshelf'" +echo "" +echo "2. Rsync data FROM replicant (SSH into replicant first):" +echo " ssh replicant" +echo " rsync -avP maddox@192.168.1.252:/home/maddox/docker/appdata/calibre/ /home/maddox/docker/appdata/calibre/" +echo " rsync -avP maddox@192.168.1.252:/home/maddox/docker/appdata/audiobookshelf/ /home/maddox/docker/appdata/audiobookshelf/" +echo "" +echo "3. Deploy services:" +echo " cd ~/clustered-fucks" +echo " ansible-playbook playbooks/deploy-calibre.yml" +echo " ansible-playbook playbooks/deploy-audiobookshelf.yml" +echo "" +echo "4. Verify services:" +echo " curl -s -o /dev/null -w '%{http_code}' http://192.168.1.80:28080/ # calibre desktop" +echo " curl -s -o /dev/null -w '%{http_code}' http://192.168.1.80:28083/ # calibre-web" +echo " curl -s -o /dev/null -w '%{http_code}' http://192.168.1.80:13378/ # audiobookshelf" +echo "" +echo "5. Update Traefik backends to point to 192.168.1.80" +echo "" +echo "6. Cleanup alien (after verification):" +echo " ssh alien 'docker rm calibre-server calibre-web audiobookshelf'" +echo "" +echo "7. Commit changes:" +echo " git add -A && git commit -m 'Phase 2: migrate calibre stack + audiobookshelf' && git push" +echo "" +echo "=== IMPORTANT ===" +echo "Change GUAC_PASS in calibre compose before deploying!"