diff --git a/synology_upgrade_ssh.yml b/synology_upgrade_ssh.yml index a1f0b10..bbb82f6 100644 --- a/synology_upgrade_ssh.yml +++ b/synology_upgrade_ssh.yml @@ -1,12 +1,12 @@ --- -- name: Synology DSM - télécharger puis installer une mise à jour via SSH (robuste Semaphore) +- name: Synology DSM - télécharger puis installer mise à jour via SSH (robuste Semaphore) hosts: synology gather_facts: false vars: - download_timeout: 3600 - reboot_timeout: 1800 - poll_delay: 20 + download_timeout: 7200 # 2h max pour download + reboot_timeout: 2400 # 40 min max reboot/upgrade + poll_delay: 30 # intervalle de polling synoupgrade_candidates: - "/usr/syno/sbin/synoupgrade" @@ -26,9 +26,9 @@ - "--install" tasks: - - name: Détecter synoupgrade (sans [ ] ; fallback PATH) - ansible.builtin.raw: | - set -e + - name: Détecter synoupgrade (sans [ ]) + ansible.builtin.shell: | + set -eu FOUND="" for p in {{ synoupgrade_candidates | join(' ') }}; do if test -x "$p"; then @@ -45,122 +45,148 @@ fi fi - echo "FOUND=$FOUND" + echo "$FOUND" + args: + executable: /bin/sh register: detect changed_when: false - - name: Enregistrer le chemin synoupgrade détecté + - name: Enregistrer le binaire synoupgrade ansible.builtin.set_fact: - synoupgrade_bin: "{{ (detect.stdout | regex_search('FOUND=(.*)', '\\1')) | default('') | trim }}" + synoupgrade_bin: "{{ detect.stdout | trim }}" changed_when: false - name: Fail si synoupgrade introuvable ansible.builtin.fail: - msg: >- - synoupgrade introuvable. Sortie détection: - {{ detect.stdout | default('') }} + msg: "synoupgrade introuvable. stdout='{{ detect.stdout }}' stderr='{{ detect.stderr }}'" when: synoupgrade_bin == "" - - name: Afficher le binaire trouvé + - name: Afficher le binaire retenu ansible.builtin.debug: msg: "synoupgrade utilisé: {{ synoupgrade_bin }}" # 1) Check update - name: Check mise à jour disponible - ansible.builtin.raw: "{{ synoupgrade_bin }} --check || true" + ansible.builtin.shell: | + set -eu + {{ synoupgrade_bin }} --check || true + args: + executable: /bin/sh register: check_out changed_when: false - - name: Afficher le résultat du check + - name: Afficher stdout/stderr du check ansible.builtin.debug: - var: check_out.stdout_lines + msg: + - "CHECK STDOUT: {{ check_out.stdout | default('') }}" + - "CHECK STDERR: {{ check_out.stderr | default('') }}" - # Si check ne renvoie rien, on NE STOP pas automatiquement, car certains DSM sont muets. - # On stop seulement si on voit des motifs "no update". + # Heuristique: stop uniquement si on voit clairement "up to date" - name: Stop si aucune mise à jour détectée (heuristique) ansible.builtin.meta: end_play when: > - (check_out.stdout | lower) is search('no update') - or (check_out.stdout | lower) is search('no newer') + (check_out.stdout | lower) is search('up to date') + or (check_out.stdout | lower) is search('no update') or (check_out.stdout | lower) is search('already') - or (check_out.stdout | lower) is search('up to date') + or (check_out.stdout | lower) is search('no newer') # 2) Download - name: Lancer le téléchargement - ansible.builtin.raw: "{{ synoupgrade_bin }} --download" + ansible.builtin.shell: | + set -eu + {{ synoupgrade_bin }} --download + args: + executable: /bin/sh register: download_start changed_when: true - - name: Afficher retour download start + - name: Afficher stdout/stderr du download start ansible.builtin.debug: - var: download_start.stdout_lines + msg: + - "DL START STDOUT: {{ download_start.stdout | default('') }}" + - "DL START STDERR: {{ download_start.stderr | default('') }}" - # 3) Poll download - - name: Attendre fin du téléchargement (status/log) - ansible.builtin.raw: | - set -e + # 3) Poll download until "ready" + - name: Attendre fin du téléchargement (status ou logs) + ansible.builtin.shell: | + set -eu if {{ synoupgrade_bin }} --status >/dev/null 2>&1; then {{ synoupgrade_bin }} --status || true else - (tail -n 80 /var/log/synoupgrade.log 2>/dev/null || true) - (tail -n 120 /var/log/messages 2>/dev/null | grep -i upgrad || true) + (tail -n 120 /var/log/synoupgrade.log 2>/dev/null || true) fi + args: + executable: /bin/sh register: dl_poll changed_when: false until: > (dl_poll.stdout | lower) is search('downloaded') or (dl_poll.stdout | lower) is search('ready') - or (dl_poll.stdout | lower) is search('finish') + or (dl_poll.stdout | lower) is search('complete') or (dl_poll.stdout | lower) is search('completed') + or (dl_poll.stdout | lower) is search('finish') or (dl_poll.stdout | lower) is search('done') retries: "{{ (download_timeout // poll_delay) | int }}" delay: "{{ poll_delay }}" - name: Afficher statut fin téléchargement ansible.builtin.debug: - var: dl_poll.stdout_lines + msg: "{{ dl_poll.stdout_lines | default([]) }}" # 4) Start install (best effort) - - name: Tenter de démarrer l'installation (plusieurs sous-commandes) - ansible.builtin.raw: "{{ synoupgrade_bin }} {{ item }} || true" + - name: Tenter de démarrer l'installation (plusieurs méthodes) + ansible.builtin.shell: | + set -eu + {{ synoupgrade_bin }} {{ item }} || true + args: + executable: /bin/sh loop: "{{ start_subcommands }}" register: start_attempts changed_when: true failed_when: false - - name: Choisir la première tentative OK (pas de mots d'erreur) + - name: Afficher résultats des tentatives start + ansible.builtin.debug: + msg: > + {{ start_attempts.results + | map(attribute='item') + | zip(start_attempts.results | map(attribute='stdout')) + | list }} + + - name: Choisir la première tentative qui ne contient pas "invalid/unknown/usage/error" ansible.builtin.set_fact: start_ok: >- {{ (start_attempts.results - | rejectattr('stdout', 'search', '(?i)(invalid|unknown|usage|not found|permission|error|failed)') - | rejectattr('stderr', 'search', '(?i)(invalid|unknown|usage|not found|permission|error|failed)') + | rejectattr('stdout', 'search', '(?i)(invalid|unknown|usage|error|failed)') + | rejectattr('stderr', 'search', '(?i)(invalid|unknown|usage|error|failed)') | list | first) | default({}) }} + changed_when: false - - name: Fail si aucune commande start ne marche + - name: Fail si aucune tentative start ne marche ansible.builtin.fail: - msg: >- - Impossible de démarrer l'installation via synoupgrade. - stdout: {{ start_attempts.results | map(attribute='stdout') | list }} - stderr: {{ start_attempts.results | map(attribute='stderr') | list }} + msg: > + Impossible de démarrer l'installation. + Détails stdout/stderr: + {{ start_attempts.results | to_nice_json }} when: start_ok | length == 0 - name: Afficher la commande start retenue ansible.builtin.debug: msg: - - "Commande retenue: {{ start_ok.item }}" + - "Start retenu: {{ start_ok.item }}" - "stdout: {{ start_ok.stdout | default('') }}" - "stderr: {{ start_ok.stderr | default('') }}" - # 5) Wait reboot (SSH down/up) - - name: Attendre que le NAS coupe SSH (reboot probable) + # 5) Attendre reboot (SSH down/up) + - name: Attendre que SSH tombe (reboot probable) ansible.builtin.wait_for: host: "{{ inventory_hostname }}" port: 22 state: drained - timeout: 300 + timeout: 600 delegate_to: localhost - name: Attendre le retour SSH @@ -172,11 +198,15 @@ delegate_to: localhost # 6) Post-check - - name: Post-check version / statut - ansible.builtin.raw: | - uname -a || true + - name: Post-check version DSM + ansible.builtin.shell: | + set -eu + echo "=== /etc/VERSION ===" cat /etc/VERSION 2>/dev/null || true + echo "=== synoupgrade --status ===" {{ synoupgrade_bin }} --status 2>/dev/null || true + args: + executable: /bin/sh register: post changed_when: false