diff --git a/life-church/playbooks/thinkcenter-reaper.yml b/life-church/playbooks/thinkcenter-reaper.yml new file mode 100644 index 0000000..07b9aba --- /dev/null +++ b/life-church/playbooks/thinkcenter-reaper.yml @@ -0,0 +1,283 @@ +--- +# ============================================================================ +# life-church/playbooks/thinkcenter-reaper.yml +# Install REAPER DAW (7.79) system-wide on thinkcenter, plus the SWS/S&M +# extension and ReaPack into the login user's UserPlugins folder. +# thinkcenter ONLY. Idempotent. become password from Infisical /lc-hosts. +# +# - REAPER: tarball from reaper.fm -> install-reaper.sh --quiet into /opt. +# - SWS 2.14.0.4 (stable) + ReaPack 1.2.6: single .so files dropped into +# ~/.config/REAPER/UserPlugins/ (per-user, owned by the login user). +# - Audio: ensures pipewire-jack so REAPER's JACK backend maps to PipeWire, +# and adds the user to the `audio` group for RT scheduling. +# +# UNDO: +# sudo /opt/REAPER/uninstall-reaper.sh (or: sudo rm -rf /opt/REAPER /usr/local/bin/reaper) +# rm -f ~/.config/REAPER/UserPlugins/reaper_sws-x86_64.so +# rm -f ~/.config/REAPER/UserPlugins/reaper_reapack-x86_64.so +# ============================================================================ + +# --- Play 1 (localhost): fetch thinkcenter sudo pass + user from Infisical --- +- name: "thinkcenter REAPER — resolve become password from Infisical" + hosts: localhost + gather_facts: false + tasks: + - name: "[resolve] Infisical creds from env" + ansible.builtin.set_fact: + infisical_client_id: "{{ lookup('env','INFISICAL_CLIENT_ID') }}" + infisical_client_secret: "{{ lookup('env','INFISICAL_CLIENT_SECRET') }}" + infisical_project_id: "{{ lookup('env','INFISICAL_PROJECT_ID') }}" + infisical_url: "{{ lookup('env','INFISICAL_API_URL') | default('http://192.168.122.41:8080', true) }}" + infisical_env_slug: "{{ lookup('env','INFISICAL_ENV_SLUG') | default('prod', true) }}" + + - name: "[resolve] Assert Infisical creds present" + ansible.builtin.assert: + that: + - infisical_client_id | length > 0 + - infisical_client_secret | length > 0 + - infisical_project_id | length > 0 + fail_msg: "Infisical creds missing from Semaphore environment 'infisical-dev'." + + - name: "[infisical] Log in" + infisical.vault.login: + url: "{{ infisical_url }}" + auth_method: universal_auth + universal_auth_client_id: "{{ infisical_client_id }}" + universal_auth_client_secret: "{{ infisical_client_secret }}" + register: infisical_login + no_log: true + + - name: "[infisical] Read /lc-hosts" + infisical.vault.read_secrets: + login_data: "{{ infisical_login.login_data }}" + project_id: "{{ infisical_project_id }}" + env_slug: "{{ infisical_env_slug }}" + path: "/lc-hosts" + as_dict: true + register: lc_secrets + no_log: true + + - name: "[set] Stash thinkcenter sudo pass + login user" + ansible.builtin.set_fact: + thinkcenter_become_pass: "{{ lc_secrets.secrets['THINKCENTER_SUDO_PASS'] }}" + thinkcenter_login_user: "{{ lc_secrets.secrets['THINKCENTER_USER'] }}" + no_log: true + +# --- Play 2: REAPER + SWS + ReaPack on thinkcenter ONLY ---------------------- +- name: "Install REAPER + SWS + ReaPack on thinkcenter" + hosts: thinkcenter + gather_facts: true + become: true + vars: + ansible_become_pass: "{{ hostvars['localhost'].thinkcenter_become_pass }}" + reaper_user: "{{ hostvars['localhost'].thinkcenter_login_user }}" + reaper_version: "779" # 7.79 (dot removed) + reaper_url: "https://www.reaper.fm/files/7.x/reaper779_linux_x86_64.tar.xz" + reaper_install_dir: /opt + reaper_binary: /opt/REAPER/reaper + sws_url: "https://www.sws-extension.org/download/pre-release/sws-2.14.0.4-Linux-x86_64-e04d6ff1.tar.xz" + sws_so_name: reaper_sws-x86_64.so + reapack_url: "https://github.com/cfillion/reapack/releases/download/v1.2.6/reaper_reapack-x86_64.so" + reapack_sha256: "35d80f63d8174c964af589c7d87c4728aa18f06899dce873e33f8d552d1bc7e0" + tmp_dir: /tmp/reaper-install + tasks: + - name: "[safety] Confirm we are on thinkcenter" + ansible.builtin.assert: + that: inventory_hostname == 'thinkcenter' + fail_msg: "Refusing to run — this play is scoped to thinkcenter only." + + - name: "[safety] Assert reaper_user resolved + exists" + block: + - name: "[safety] Assert non-empty" + ansible.builtin.assert: + that: reaper_user | length > 0 + fail_msg: "reaper_user empty — THINKCENTER_USER not read from Infisical." + - name: "[facts] getent the user" + ansible.builtin.getent: + database: passwd + key: "{{ reaper_user }}" + - name: "[facts] Assert user exists" + ansible.builtin.assert: + that: reaper_user in ansible_facts.getent_passwd + fail_msg: "User '{{ reaper_user }}' does not exist on host." + + - name: "[deps] Ensure runtime deps + PipeWire-JACK bridge (retry)" + ansible.builtin.apt: + name: + - xz-utils + - libstdc++6 + - libgl1 + - libasound2t64 + - pipewire-jack + state: present + update_cache: true + cache_valid_time: 3600 + register: deps + until: deps is succeeded + retries: 3 + delay: 15 + + - name: "[audio] Add {{ reaper_user }} to the audio group (RT scheduling)" + ansible.builtin.user: + name: "{{ reaper_user }}" + groups: audio + append: true + + # ---------- REAPER core ---------- + - name: "[reaper] Is REAPER already installed at {{ reaper_binary }}?" + ansible.builtin.stat: + path: "{{ reaper_binary }}" + register: reaper_present + + - name: "[reaper] Check installed version (if present)" + ansible.builtin.command: "{{ reaper_binary }} -version" + register: reaper_installed_ver + changed_when: false + failed_when: false + when: reaper_present.stat.exists + + - name: "[reaper] Create temp dir" + ansible.builtin.file: + path: "{{ tmp_dir }}" + state: directory + mode: "0755" + when: not reaper_present.stat.exists + + - name: "[reaper] Download REAPER {{ reaper_version }} tarball (retry)" + ansible.builtin.get_url: + url: "{{ reaper_url }}" + dest: "{{ tmp_dir }}/reaper.tar.xz" + mode: "0644" + register: reaper_dl + until: reaper_dl is succeeded + retries: 3 + delay: 15 + when: not reaper_present.stat.exists + + - name: "[reaper] Extract tarball" + ansible.builtin.unarchive: + src: "{{ tmp_dir }}/reaper.tar.xz" + dest: "{{ tmp_dir }}" + remote_src: true + when: not reaper_present.stat.exists + + - name: "[reaper] Run installer unattended (--quiet, desktop integration)" + ansible.builtin.command: + cmd: "./install-reaper.sh --install {{ reaper_install_dir }} --integrate-desktop --usr-local-bin-symlink --quiet" + chdir: "{{ tmp_dir }}/reaper_linux_x86_64" + register: reaper_installer + when: not reaper_present.stat.exists + changed_when: reaper_installer.rc == 0 + + - name: "[reaper] Verify binary now exists" + ansible.builtin.stat: + path: "{{ reaper_binary }}" + register: reaper_after + + - name: "[reaper] Assert REAPER installed" + ansible.builtin.assert: + that: reaper_after.stat.exists + fail_msg: "REAPER binary not found at {{ reaper_binary }} after install." + success_msg: "REAPER present at {{ reaper_binary }}." + + # ---------- User plugin dir ---------- + - name: "[plugins] Resolve user home" + ansible.builtin.set_fact: + reaper_home: "{{ ansible_facts.getent_passwd[reaper_user][4] }}" + + - name: "[plugins] Ensure UserPlugins dir exists (owned by {{ reaper_user }})" + ansible.builtin.file: + path: "{{ reaper_home }}/.config/REAPER/UserPlugins" + state: directory + owner: "{{ reaper_user }}" + group: "{{ reaper_user }}" + mode: "0755" + + # ---------- SWS extension ---------- + - name: "[sws] Download SWS tarball (retry)" + ansible.builtin.get_url: + url: "{{ sws_url }}" + dest: "{{ tmp_dir }}/sws.tar.xz" + mode: "0644" + register: sws_dl + until: sws_dl is succeeded + retries: 3 + delay: 15 + + - name: "[sws] Create SWS extract dir" + ansible.builtin.file: + path: "{{ tmp_dir }}/sws" + state: directory + mode: "0755" + + - name: "[sws] Extract SWS" + ansible.builtin.unarchive: + src: "{{ tmp_dir }}/sws.tar.xz" + dest: "{{ tmp_dir }}/sws" + remote_src: true + + - name: "[sws] Locate the SWS .so inside the archive" + ansible.builtin.find: + paths: "{{ tmp_dir }}/sws" + patterns: "reaper_sws*.so" + recurse: true + register: sws_found + + - name: "[sws] Assert SWS .so found" + ansible.builtin.assert: + that: sws_found.files | length > 0 + fail_msg: "Could not find reaper_sws*.so in the SWS archive." + + - name: "[sws] Install SWS .so into UserPlugins" + ansible.builtin.copy: + src: "{{ sws_found.files[0].path }}" + dest: "{{ reaper_home }}/.config/REAPER/UserPlugins/{{ sws_so_name }}" + owner: "{{ reaper_user }}" + group: "{{ reaper_user }}" + mode: "0644" + remote_src: true + register: sws_install + + # ---------- ReaPack ---------- + - name: "[reapack] Download ReaPack .so with checksum verification (retry)" + ansible.builtin.get_url: + url: "{{ reapack_url }}" + dest: "{{ reaper_home }}/.config/REAPER/UserPlugins/reaper_reapack-x86_64.so" + checksum: "sha256:{{ reapack_sha256 }}" + owner: "{{ reaper_user }}" + group: "{{ reaper_user }}" + mode: "0644" + register: reapack_install + until: reapack_install is succeeded + retries: 3 + delay: 15 + + # ---------- Verify + cleanup ---------- + - name: "[verify] List installed REAPER user plugins" + ansible.builtin.find: + paths: "{{ reaper_home }}/.config/REAPER/UserPlugins" + patterns: "*.so" + register: installed_plugins + + - name: "[verify] Assert both extensions present" + ansible.builtin.assert: + that: + - installed_plugins.files | map(attribute='path') | select('search','reaper_sws') | list | length > 0 + - installed_plugins.files | map(attribute='path') | select('search','reaper_reapack') | list | length > 0 + fail_msg: "SWS and/or ReaPack .so missing from UserPlugins." + success_msg: "SWS + ReaPack installed into UserPlugins." + + - name: "[cleanup] Remove temp install dir" + ansible.builtin.file: + path: "{{ tmp_dir }}" + state: absent + + - name: "[report] REAPER stack summary" + ansible.builtin.debug: + msg: + - "===== thinkcenter REAPER stack =====" + - "REAPER binary : {{ reaper_binary }} ({{ 'newly installed' if not reaper_present.stat.exists else 'already present' }})" + - "UserPlugins dir : {{ reaper_home }}/.config/REAPER/UserPlugins" + - "installed .so files : {{ installed_plugins.files | map(attribute='path') | map('basename') | list }}" + - "audio group : {{ reaper_user }} added (log out/in for RT scheduling to take effect)" + - "NOTE: launch REAPER, set Preferences->Audio->Device to JACK (PipeWire bridge) or ALSA. Extensions appear under the Extensions menu."