diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index 0af92ec..8e750c2 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -6,6 +6,8 @@ tags: prometheus - role: grafana tags: grafana + - role: transmission + tags: transmission - role: kiwix tags: kiwix - role: borgmatic diff --git a/ansible/roles/kiwix/defaults/main.yml b/ansible/roles/kiwix/defaults/main.yml index a228b2e..c88fd16 100644 --- a/ansible/roles/kiwix/defaults/main.yml +++ b/ansible/roles/kiwix/defaults/main.yml @@ -4,8 +4,14 @@ kiwix_zim_dir: /Users/erichblume/code/3rd/kiwix-tools kiwix_port: 5501 kiwix_log_dir: /Users/erichblume/Library/Logs +# Transmission integration +# When enabled, ZIM archives are downloaded via BitTorrent instead of direct HTTP +kiwix_use_transmission: true +kiwix_torrent_base_url: "https://download.kiwix.org/zim" + # ZIM archives to download and serve -# Base URL: https://download.kiwix.org/zim/ +# Each item needs: category, filename +# Torrent URL: {{ kiwix_torrent_base_url }}/{{ category }}/{{ filename }}.torrent kiwix_zim_archives: # Wikipedia - Top 1M articles with images (43G) - category: wikipedia diff --git a/ansible/roles/kiwix/meta/main.yml b/ansible/roles/kiwix/meta/main.yml new file mode 100644 index 0000000..32004b6 --- /dev/null +++ b/ansible/roles/kiwix/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: transmission diff --git a/ansible/roles/kiwix/tasks/main.yml b/ansible/roles/kiwix/tasks/main.yml index 6981408..b1637f4 100644 --- a/ansible/roles/kiwix/tasks/main.yml +++ b/ansible/roles/kiwix/tasks/main.yml @@ -5,7 +5,135 @@ state: directory mode: '0755' -- name: Check which ZIM archives exist +# --- Transmission-based download logic --- +- name: Check transmission daemon is responding + ansible.builtin.command: transmission-remote -l + register: transmission_check + changed_when: false + failed_when: false + when: kiwix_use_transmission + +- name: Fail if transmission is not running + ansible.builtin.fail: + msg: "Transmission daemon is not responding. Ensure transmission role ran successfully." + when: kiwix_use_transmission and transmission_check.rc != 0 + +# Check if each torrent is already loaded in transmission +- name: Check torrent status for each ZIM archive + ansible.builtin.shell: | + # Look for the torrent by filename (name column in transmission-remote -l) + # Output: "not_found", "downloading XX%", or "complete" + torrent_line=$(transmission-remote -l | grep -F "{{ item.filename | regex_replace('\\.zim$', '') }}" || true) + if [ -z "$torrent_line" ]; then + echo "not_found" + else + # Extract percentage from the Done column (2nd column) + pct=$(echo "$torrent_line" | awk '{print $2}') + if [ "$pct" = "100%" ]; then + echo "complete" + else + echo "downloading $pct" + fi + fi + args: + executable: /bin/bash + loop: "{{ kiwix_zim_archives }}" + loop_control: + label: "{{ item.filename }}" + register: torrent_status + changed_when: false + when: kiwix_use_transmission + +# Add torrents that are not yet loaded +- name: Add missing torrents to transmission + ansible.builtin.command: > + transmission-remote -a "{{ kiwix_torrent_base_url }}/{{ item.item.category }}/{{ item.item.filename }}.torrent" + loop: "{{ torrent_status.results | default([]) }}" + loop_control: + label: "{{ item.item.filename }}" + when: + - kiwix_use_transmission + - item.stdout is defined + - item.stdout == "not_found" + register: torrent_add + changed_when: torrent_add.rc == 0 + +# Wait briefly and recheck status for newly added torrents +- name: Wait for transmission to register new torrents + ansible.builtin.pause: + seconds: 5 + when: + - kiwix_use_transmission + - torrent_add.changed is defined + - torrent_add.changed + +# Recheck all torrent statuses +- name: Recheck torrent status after adding + ansible.builtin.shell: | + torrent_line=$(transmission-remote -l | grep -F "{{ item.filename | regex_replace('\\.zim$', '') }}" || true) + if [ -z "$torrent_line" ]; then + echo "not_found" + else + pct=$(echo "$torrent_line" | awk '{print $2}') + if [ "$pct" = "100%" ]; then + echo "complete" + else + echo "downloading $pct" + fi + fi + args: + executable: /bin/bash + loop: "{{ kiwix_zim_archives }}" + loop_control: + label: "{{ item.filename }}" + register: torrent_status_final + changed_when: false + when: kiwix_use_transmission + +# Check if symlink already exists for completed downloads +- name: Check if ZIM symlink exists + ansible.builtin.stat: + path: "{{ kiwix_zim_dir }}/{{ item.item.filename }}" + get_checksum: false + loop: "{{ torrent_status_final.results | default([]) }}" + loop_control: + label: "{{ item.item.filename }}" + register: zim_symlink_stat + when: + - kiwix_use_transmission + - item.stdout is defined + - item.stdout == "complete" + +# Create symlinks for completed downloads +- name: Symlink completed ZIM downloads to kiwix directory + ansible.builtin.file: + src: "{{ transmission_download_dir }}/{{ item.item.item.filename }}" + dest: "{{ kiwix_zim_dir }}/{{ item.item.item.filename }}" + state: link + loop: "{{ zim_symlink_stat.results | default([]) }}" + loop_control: + label: "{{ item.item.item.filename | default('unknown') }}" + when: + - kiwix_use_transmission + - item.stat is defined + - not item.stat.exists + notify: restart kiwix-serve + +# Report on incomplete downloads (informational, no failure) +- name: Report incomplete torrent downloads + ansible.builtin.debug: + msg: "Torrent still downloading: {{ item.item.filename }} ({{ item.stdout }})" + loop: "{{ torrent_status_final.results | default([]) }}" + loop_control: + label: "{{ item.item.filename }}" + when: + - kiwix_use_transmission + - item.stdout is defined + - item.stdout != "complete" + - item.stdout != "not_found" + +# --- Fallback: Direct HTTP download (original behavior) --- +- name: Check which ZIM archives exist (direct download mode) ansible.builtin.stat: path: "{{ kiwix_zim_dir }}/{{ item.filename }}" get_checksum: false @@ -13,19 +141,24 @@ loop_control: label: "{{ item.filename }}" register: zim_stat + when: not kiwix_use_transmission -- name: Download missing ZIM archives +- name: Download missing ZIM archives (direct download mode) ansible.builtin.get_url: url: "https://download.kiwix.org/zim/{{ item.item.category }}/{{ item.item.filename }}" dest: "{{ kiwix_zim_dir }}/{{ item.item.filename }}" mode: '0644' timeout: 3600 - loop: "{{ zim_stat.results }}" + loop: "{{ zim_stat.results | default([]) }}" loop_control: - label: "{{ item.item.filename }}" - when: not item.stat.exists + label: "{{ item.item.filename | default('unknown') }}" + when: + - not kiwix_use_transmission + - item.stat is defined + - not item.stat.exists notify: restart kiwix-serve +# --- LaunchAgent deployment --- - name: Deploy kiwix-serve LaunchAgent plist ansible.builtin.template: src: kiwix-serve.plist.j2 diff --git a/ansible/roles/transmission/defaults/main.yml b/ansible/roles/transmission/defaults/main.yml new file mode 100644 index 0000000..1c6d83c --- /dev/null +++ b/ansible/roles/transmission/defaults/main.yml @@ -0,0 +1,22 @@ +--- +# Transmission download directories +transmission_download_dir: /Users/erichblume/transmission +transmission_incomplete_dir: /Users/erichblume/transmission/.incomplete +transmission_config_dir: /Users/erichblume/.config/transmission-daemon + +# RPC settings (local only - no authentication needed) +transmission_rpc_enabled: true +transmission_rpc_port: 9091 +transmission_rpc_bind_address: "127.0.0.1" +transmission_rpc_authentication_required: false +transmission_rpc_whitelist_enabled: true +transmission_rpc_whitelist: "127.0.0.1" + +# Speed limits (KB/s, 0 = unlimited) +transmission_speed_limit_down: 0 +transmission_speed_limit_up: 100 + +# P2P settings +transmission_dht_enabled: true +transmission_pex_enabled: true +transmission_encryption: 1 # 0=prefer unencrypted, 1=prefer encrypted, 2=require encrypted diff --git a/ansible/roles/transmission/handlers/main.yml b/ansible/roles/transmission/handlers/main.yml new file mode 100644 index 0000000..d2c0d1c --- /dev/null +++ b/ansible/roles/transmission/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: restart transmission + ansible.builtin.command: brew services restart transmission-cli diff --git a/ansible/roles/transmission/tasks/main.yml b/ansible/roles/transmission/tasks/main.yml new file mode 100644 index 0000000..f4e7da3 --- /dev/null +++ b/ansible/roles/transmission/tasks/main.yml @@ -0,0 +1,42 @@ +--- +- name: Install transmission-cli via homebrew + community.general.homebrew: + name: transmission-cli + state: present + +- name: Ensure transmission download directory exists + ansible.builtin.file: + path: "{{ transmission_download_dir }}" + state: directory + mode: '0755' + +- name: Ensure transmission incomplete directory exists + ansible.builtin.file: + path: "{{ transmission_incomplete_dir }}" + state: directory + mode: '0755' + +- name: Ensure transmission config directory exists + ansible.builtin.file: + path: "{{ transmission_config_dir }}" + state: directory + mode: '0755' + +- name: Stop transmission before config changes + ansible.builtin.command: brew services stop transmission-cli + register: brew_stop + changed_when: false + failed_when: false + +- name: Deploy transmission settings.json + ansible.builtin.template: + src: settings.json.j2 + dest: "{{ transmission_config_dir }}/settings.json" + mode: '0644' + notify: restart transmission + +- name: Ensure transmission service is started + ansible.builtin.command: brew services start transmission-cli + register: brew_start + changed_when: "'Successfully started' in brew_start.stdout" + failed_when: false diff --git a/ansible/roles/transmission/templates/settings.json.j2 b/ansible/roles/transmission/templates/settings.json.j2 new file mode 100644 index 0000000..6875415 --- /dev/null +++ b/ansible/roles/transmission/templates/settings.json.j2 @@ -0,0 +1,21 @@ +{ + "_comment": "{{ ansible_managed }}", + "download-dir": "{{ transmission_download_dir }}", + "incomplete-dir": "{{ transmission_incomplete_dir }}", + "incomplete-dir-enabled": true, + "dht-enabled": {{ transmission_dht_enabled | lower }}, + "pex-enabled": {{ transmission_pex_enabled | lower }}, + "encryption": {{ transmission_encryption }}, + "rpc-enabled": {{ transmission_rpc_enabled | lower }}, + "rpc-port": {{ transmission_rpc_port }}, + "rpc-bind-address": "{{ transmission_rpc_bind_address }}", + "rpc-authentication-required": {{ transmission_rpc_authentication_required | lower }}, + "rpc-whitelist-enabled": {{ transmission_rpc_whitelist_enabled | lower }}, + "rpc-whitelist": "{{ transmission_rpc_whitelist }}", + "speed-limit-down": {{ transmission_speed_limit_down }}, + "speed-limit-down-enabled": {{ (transmission_speed_limit_down > 0) | lower }}, + "speed-limit-up": {{ transmission_speed_limit_up }}, + "speed-limit-up-enabled": {{ (transmission_speed_limit_up > 0) | lower }}, + "start-added-torrents": true, + "trash-original-torrent-files": false +}