# update_bettershift.yml - name: Update BetterShift on VM via Proxmox hosts: linux_servers gather_facts: false become: true become_user: root become_method: sudo vars: # VM connection (provided by Semaphore env vars) vm_ip: "{{ lookup('env', 'VM_IP') }}" vm_user: "{{ lookup('env', 'VM_USER') }}" vm_pass: "{{ lookup('env', 'VM_PASS') }}" use_sudo: false # Debug mode (controlled via Semaphore variable) DEBUG: "{{ lookup('env', 'DEBUG') | default(0) | int }}" RETRIES: "{{ lookup('env', 'RETRIES') | default(25) | int }}" # BetterShift specifics bettershift_project: "bettershift" bettershift_compose_file: "/home/howard/.ansible-compose/docker-compose/docker-compose-bettershift.yml" bettershift_service: "bettershift" bettershift_image: "ghcr.io/pantelx/bettershift:v2.2.1" bettershift_container_name: "bettershift" bettershift_port: 3067 bettershift_data_dir: "/data/compose/bettershift/data" # Optional external URL for controller-side readiness check # In Semaphore you can set: # BETTERSHIFT_URL=https://bettershift.martinfencl.eu bettershift_url: "{{ lookup('env', 'BETTERSHIFT_URL') | default('https://bettershift.martinfencl.eu', true) }}" # Retry policy bettershift_retries: "{{ RETRIES }}" bettershift_delay: 2 # Docker command prefix (consistent behavior) docker_prefix: "unalias docker 2>/dev/null || true; DOCKER_CLI_HINTS=0; command docker" # Compose command bettershift_compose_cmd: >- {{ docker_prefix }} compose -p {{ bettershift_project }} -f {{ bettershift_compose_file }} # Commands to run on the target VM bettershift_commands: - "mkdir -p {{ bettershift_data_dir }}" - "{{ docker_prefix }} pull -q {{ bettershift_image }} >/dev/null" - "{{ bettershift_compose_cmd }} config >/dev/null" - "{{ bettershift_compose_cmd }} pull {{ bettershift_service }} >/dev/null" - "{{ bettershift_compose_cmd }} up -d --no-deps --force-recreate {{ bettershift_service }} >/dev/null" tasks: - name: Ensure sshpass is installed (for password-based SSH) ansible.builtin.apt: name: sshpass state: present update_cache: yes - name: Run BetterShift update commands on VM (via SSH) ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - ConnectTimeout=15 - "{{ vm_user }}@{{ vm_ip }}" - bash - -lc - "{{ ('sudo ' if use_sudo else '') + item }}" environment: SSHPASS: "{{ vm_pass }}" loop: "{{ bettershift_commands }}" loop_control: index_var: idx label: "cmd-{{ idx }}" register: bettershift_cmds changed_when: false no_log: "{{ DEBUG == 0 }}" - name: Show outputs for each BetterShift command ansible.builtin.debug: msg: | CMD: {{ item.item }} RC: {{ item.rc }} STDOUT: {{ (item.stdout | default('')).strip() }} STDERR: {{ (item.stderr | default('')).strip() }} loop: "{{ bettershift_cmds.results }}" when: DEBUG == 1 - name: Fail play if any BetterShift command failed ansible.builtin.assert: that: "item.rc == 0" fail_msg: "BetterShift update failed on VM: {{ item.item }} (rc={{ item.rc }})" success_msg: "All BetterShift update commands succeeded." loop: "{{ bettershift_cmds.results }}" loop_control: index_var: idx label: "cmd-{{ idx }}" # ------------------------- # Readiness checks (controller first, then VM fallback) # ------------------------- - name: BetterShift | Wait for web app (controller first, with retries) ansible.builtin.uri: url: "{{ (bettershift_url | regex_replace('/$','')) + '/' }}" method: GET return_content: true validate_certs: "{{ (bettershift_url | default('')) is match('^https://') }}" status_code: - 200 - 307 - 308 register: bettershift_controller delegate_to: localhost run_once: true when: bettershift_url is defined and (bettershift_url | length) > 0 retries: "{{ bettershift_retries }}" delay: "{{ bettershift_delay }}" until: bettershift_controller.status in [200, 307, 308] failed_when: false changed_when: false - name: BetterShift | VM-side fetch (HTML via Python, with retries) ansible.builtin.command: argv: - sshpass - -e - ssh - -o - StrictHostKeyChecking=no - -o - ConnectTimeout=15 - "{{ vm_user }}@{{ vm_ip }}" - bash - -lc - | python3 - <<'PY' # Fetch BetterShift homepage from localhost and print a compact status line import urllib.request import urllib.error import sys url = "http://127.0.0.1:{{ bettershift_port }}/" try: req = urllib.request.Request(url, method="GET") with urllib.request.urlopen(req, timeout=15) as r: body = r.read().decode(errors="ignore") sys.stdout.write(f"STATUS={r.status}\n") sys.stdout.write(body[:1000]) except urllib.error.HTTPError as e: body = e.read().decode(errors="ignore") sys.stdout.write(f"STATUS={e.code}\n") sys.stdout.write(body[:1000]) except Exception as e: sys.stdout.write(f"ERROR={type(e).__name__}: {e}\n") PY environment: SSHPASS: "{{ vm_pass }}" register: bettershift_vm changed_when: false failed_when: false when: bettershift_controller.status | default(0) not in [200, 307, 308] retries: "{{ bettershift_retries }}" delay: "{{ bettershift_delay }}" until: > ('STATUS=200' in (bettershift_vm.stdout | default(''))) or ('STATUS=307' in (bettershift_vm.stdout | default(''))) or ('STATUS=308' in (bettershift_vm.stdout | default(''))) no_log: "{{ DEBUG == 0 }}" - name: BetterShift | Print concise summary ansible.builtin.debug: msg: >- BetterShift web {{ 'OK' if ( (bettershift_controller.status | default(0)) in [200, 307, 308] or ('STATUS=200' in (bettershift_vm.stdout | default(''))) or ('STATUS=307' in (bettershift_vm.stdout | default(''))) or ('STATUS=308' in (bettershift_vm.stdout | default(''))) ) else 'NOT OK' }}. Source={{ 'controller' if (bettershift_controller.status | default(0)) in [200, 307, 308] else 'vm' if (bettershift_vm.stdout | default('') | trim | length) > 0 else 'n/a' }}; controller_status={{ bettershift_controller.status | default('n/a') }}; local_port={{ bettershift_port }}. when: DEBUG == 1 - name: BetterShift | Web unavailable (after retries) ansible.builtin.debug: msg: "BetterShift web nenĂ­ dostupnĂ˝ ani po pokusech." when: - DEBUG == 1 - (bettershift_controller.status | default(0)) not in [200, 307, 308] - "'STATUS=200' not in (bettershift_vm.stdout | default(''))" - "'STATUS=307' not in (bettershift_vm.stdout | default(''))" - "'STATUS=308' not in (bettershift_vm.stdout | default(''))" - name: BetterShift | VM response excerpt (debug) ansible.builtin.debug: msg: "{{ (bettershift_vm.stdout | default(''))[:500] }}" when: - DEBUG == 1 - bettershift_vm is defined - bettershift_vm.stdout is defined