diff --git a/nextcloud/update_uptime_kuma.yml b/nextcloud/update_uptime_kuma.yml index 4fd5e34..31b7c76 100644 --- a/nextcloud/update_uptime_kuma.yml +++ b/nextcloud/update_uptime_kuma.yml @@ -1,3 +1,4 @@ +# nextcloud/update_uptime_kuma.yml - name: Update Uptime Kuma on VM via Proxmox hosts: proxmox gather_facts: false @@ -12,23 +13,43 @@ vm_pass: "{{ lookup('env', 'VM_PASS') }}" use_sudo: false + # --- Debug toggle --- + # Set DEBUG=1 in Semaphore to see full stdout/stderr (disables no_log) + debug: "{{ (lookup('env','DEBUG') | default('0')) | bool }}" + # --- Uptime Kuma specifics --- - kuma_project: "uptime-kuma" + kuma_project: "uptime-kuma" # docker compose project name kuma_compose_file: "/data/compose/uptime-kuma.yml" - kuma_service: "uptime-kuma" + kuma_service: "uptime-kuma" # service name in the compose file + kuma_image: "louislam/uptime-kuma:latest" kuma_port: 3001 + kuma_url: "{{ lookup('env', 'KUMA_URL') | default('', true) }}" # optional public URL - # If empty/undefined, controller check is skipped and we only probe from the VM. - kuma_url: "{{ lookup('env', 'KUMA_URL') | default('', true) }}" + # Fixed container name used in your compose (conflicts if an older non-compose/Portainer container exists) + kuma_container_name: "uptime-kuma-dev" + kuma_force_replace_conflict: true # remove conflicting container automatically + kuma_remove_orphans: true # remove containers not present in the compose file - # Docker command prefix (keeps behavior consistent and quiet) + # Host path used by your volume mapping in compose (ensure it exists) + kuma_data_dir: "/data/compose/kuma/data" + + # Docker command prefix (consistent behavior and quiet hints) docker_prefix: "unalias docker 2>/dev/null || true; DOCKER_CLI_HINTS=0; command docker" - # Commands to run on the target VM + # Commands to run on the target VM (outputs are kept visible when debug=true) kuma_commands: - - "{{ docker_prefix }} pull -q louislam/uptime-kuma:latest >/dev/null" - - "{{ docker_prefix }} compose -p {{ kuma_project }} -f {{ kuma_compose_file }} pull {{ kuma_service }} >/dev/null" - - "{{ docker_prefix }} compose -p {{ kuma_project }} -f {{ kuma_compose_file }} up -d --no-deps --force-recreate {{ kuma_service }} >/dev/null" + # 0) pull image (helpful cache warm-up) + - "{{ docker_prefix }} pull -q {{ kuma_image }}" + # 1) show services resolved from the compose file (helps debugging service names) + - "{{ docker_prefix }} compose -p {{ kuma_project }} -f {{ kuma_compose_file }} config --services" + # 2) pull service + - "{{ docker_prefix }} compose -p {{ kuma_project }} -f {{ kuma_compose_file }} pull {{ kuma_service }}" + # 3) up the service (optionally remove orphans) + - >- + {{ docker_prefix }} compose -p {{ kuma_project }} -f {{ kuma_compose_file }} + up -d --no-deps --force-recreate + {{ '--remove-orphans' if kuma_remove_orphans else '' }} + {{ kuma_service }} tasks: - name: Ensure sshpass is installed (for password-based SSH) # English comments @@ -37,7 +58,135 @@ state: present update_cache: yes - - name: Run Uptime Kuma update commands on VM (via SSH) # Avoid leaking password in logs + # ------------------------- + # Preflight checks + # ------------------------- + + - name: Preflight | Ensure compose file exists on VM + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "test -r {{ kuma_compose_file }}" + register: preflight_compose_exists + changed_when: false + failed_when: preflight_compose_exists.rc != 0 + no_log: "{{ not debug }}" + + - name: Preflight | Validate compose file syntax + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "{{ docker_prefix }} compose -f {{ kuma_compose_file }} config -q" + register: preflight_compose_valid + changed_when: false + failed_when: preflight_compose_valid.rc != 0 + no_log: "{{ not debug }}" + + - name: Preflight | Ensure service exists in compose file + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - >- + {{ docker_prefix }} compose -f {{ kuma_compose_file }} config --services | grep -x {{ kuma_service }} + register: preflight_service_exists + changed_when: false + failed_when: preflight_service_exists.rc != 0 + no_log: "{{ not debug }}" + + - name: Preflight | Ensure Kuma data dir exists (host path from compose) + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "mkdir -p {{ kuma_data_dir }}" + changed_when: false + no_log: "{{ not debug }}" + + - name: Preflight | Detect conflicting container by fixed name + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "docker ps -a --filter name=^/{{ kuma_container_name }}$ --format '{{'{{'}}.ID{{'}}'}}'" + register: kuma_conflict + changed_when: false + failed_when: false + no_log: "{{ not debug }}" + + - name: Preflight | Remove conflicting container if present (and allowed) + ansible.builtin.command: + argv: + - sshpass + - -p + - "{{ vm_pass }}" + - ssh + - -o + - StrictHostKeyChecking=no + - -o + - ConnectTimeout=15 + - "{{ vm_user }}@{{ vm_ip }}" + - bash + - -lc + - "docker rm -f {{ kuma_container_name }}" + when: + - kuma_force_replace_conflict | bool + - (kuma_conflict.stdout | default('') | trim) | length > 0 + changed_when: true + no_log: "{{ not debug }}" + + # ------------------------- + # Update commands + # ------------------------- + + - name: Run Uptime Kuma update commands on VM (via SSH) # Shows details when DEBUG=1 ansible.builtin.command: argv: - sshpass @@ -55,7 +204,8 @@ loop: "{{ kuma_commands }}" register: kuma_cmds changed_when: false - no_log: true # <- hides password and raw argv from logs + ignore_errors: true + no_log: "{{ not debug }}" - name: Show summarized outputs for each command (sanitized) ansible.builtin.debug: @@ -68,17 +218,15 @@ {{ (item.stderr | default('')).strip() }} loop: "{{ kuma_cmds.results }}" vars: - # Print a redacted/short form of the command so we don't leak the password - item_short: >- - {{ - (item.cmd | map('quote') | list)[-1] # last element is the remote "bash -lc" payload - }} + # Print only the remote "bash -lc" payload (no password leaks) + item_short: "{{ (item.cmd | default([]))[-1] | default('N/A') }}" when: kuma_cmds is defined + changed_when: false - name: Fail play if any Uptime Kuma command failed ansible.builtin.assert: that: "item.rc == 0" - fail_msg: "Uptime Kuma update failed on VM: {{ (item.cmd | last) }} (rc={{ item.rc }})" + fail_msg: "Uptime Kuma update failed on VM: {{ (item.cmd | default([]))[-1] | default('N/A') }} (rc={{ item.rc }})" success_msg: "All Uptime Kuma update commands succeeded." loop: "{{ kuma_cmds.results }}" @@ -91,7 +239,6 @@ url: "{{ (kuma_url | regex_replace('/$','')) + '/' }}" method: GET return_content: true - # Validate TLS only when using https:// validate_certs: "{{ (kuma_url | default('')) is match('^https://') }}" status_code: 200 register: kuma_controller @@ -133,7 +280,6 @@ register: kuma_vm changed_when: false failed_when: false - # Run only if controller check missing or didn't succeed when: (kuma_url is not defined) or (kuma_url | length == 0) or (kuma_controller.status | default(0)) != 200 - name: Kuma | Decide readiness source