--- - name: Synology DSM - télécharger puis installer une mise à jour via SSH (auto-detect synoupgrade) hosts: synology gather_facts: false vars: # Temps max (en secondes) pour attendre download / reboot download_timeout: 3600 reboot_timeout: 1800 poll_delay: 20 # Chemins probables (DSM varie selon versions/modèles) synoupgrade_candidates: - "/usr/syno/sbin/synoupgrade" - "/usr/syno/bin/synoupgrade" - "/sbin/synoupgrade" - "/bin/synoupgrade" - "/usr/bin/synoupgrade" - "/usr/sbin/synoupgrade" - "/usr/syno/sbin/synoupgrade2" - "/usr/syno/bin/synoupgrade2" - "/usr/bin/synoupgrade2" - "/usr/sbin/synoupgrade2" # Commandes possibles pour lancer l’install (selon DSM) start_subcommands: - "--start" - "--upgrade" - "--apply" - "--run" - "--install" tasks: # 0) Détecter le binaire présent - name: Détecter synoupgrade (ou synoupgrade2) sur le NAS ansible.builtin.raw: | set -e FOUND="" for p in {{ synoupgrade_candidates | join(' ') }}; do if [ -x "$p" ]; then FOUND="$p" break fi done # fallback: PATH (busybox) if [ -z "$FOUND" ]; then if command -v synoupgrade >/dev/null 2>&1; then FOUND="$(command -v synoupgrade)" elif command -v synoupgrade2 >/dev/null 2>&1; then FOUND="$(command -v synoupgrade2)" fi fi if [ -n "$FOUND" ]; then echo "FOUND=$FOUND" exit 0 else echo "FOUND=" exit 0 fi register: detect changed_when: false - name: Enregistrer le chemin synoupgrade détecté ansible.builtin.set_fact: synoupgrade_bin: "{{ (detect.stdout | regex_search('FOUND=(.*)', '\\1')) | default('') | trim }}" changed_when: false - name: Fail si synoupgrade introuvable ansible.builtin.fail: msg: >- synoupgrade introuvable sur ce NAS (DSM/ modèle). stdout: {{ detect.stdout | default('') }} => Donne-moi la sortie de : "ls -l /usr/syno/sbin /usr/syno/bin | grep -i upgrad" when: synoupgrade_bin == "" - name: Afficher le binaire trouvé ansible.builtin.debug: msg: "synoupgrade utilisé: {{ synoupgrade_bin }}" # 1) Check mise à jour dispo - name: Check mise à jour disponible ansible.builtin.raw: "{{ synoupgrade_bin }} --check || true" register: check_out changed_when: false - name: Afficher le résultat du check ansible.builtin.debug: var: check_out.stdout_lines # Heuristique : stop si rien - 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') or (check_out.stdout | lower) is search('already') or (check_out.stdout | lower) is search('up to date') # 2) Download - name: Lancer le téléchargement ansible.builtin.raw: "{{ synoupgrade_bin }} --download" register: download_start changed_when: true - name: Afficher retour download start ansible.builtin.debug: var: download_start.stdout_lines # 3) Attendre fin download (status ou log) - name: Attendre fin du téléchargement (poll status/log) ansible.builtin.raw: | set -e 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) fi 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('completed') 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 # 4) Démarrer installation (essais) - name: Tenter de démarrer l'installation (plusieurs sous-commandes) ansible.builtin.raw: "{{ synoupgrade_bin }} {{ item }} || true" loop: "{{ start_subcommands }}" register: start_attempts changed_when: true failed_when: false - name: Choisir la première tentative qui ne ressemble pas à une erreur 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)') | list | first) | default({}) }} - name: Fail si aucune commande de démarrage 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 }} when: start_ok | length == 0 - name: Afficher la commande start retenue ansible.builtin.debug: msg: - "Commande retenue: {{ start_ok.item }}" - "stdout: {{ start_ok.stdout | default('') }}" - "stderr: {{ start_ok.stderr | default('') }}" # 5) Attente reboot : le NAS peut couper SSH - name: Attendre que le NAS coupe SSH (reboot probable) ansible.builtin.wait_for: host: "{{ inventory_hostname }}" port: 22 state: drained timeout: 300 delegate_to: localhost - name: Attendre le retour SSH ansible.builtin.wait_for: host: "{{ inventory_hostname }}" port: 22 state: started timeout: "{{ reboot_timeout }}" delegate_to: localhost # 6) Post-check - name: Post-check version / statut ansible.builtin.raw: | uname -a || true cat /etc/VERSION 2>/dev/null || true {{ synoupgrade_bin }} --status 2>/dev/null || true register: post changed_when: false - name: Afficher post-check ansible.builtin.debug: var: post.stdout_lines