--- # ============================================================================= # proxmox_restore — tasks # Returns VMs to their origin nodes using state written by proxmox_drain. # # Required vars: # current_node — the node whose VMs should be restored # restore_state_file — path to the JSON state file (set by caller or discovered) # ============================================================================= # ── Find state file ─────────────────────────────────────────────────────────── - name: "Restore | {{ current_node }} | Find state files" ansible.builtin.find: paths: "{{ restore_state_dir }}" patterns: "{{ current_node }}_*.json" file_type: file register: restore_found_files delegate_to: localhost - name: "Restore | {{ current_node }} | No state files found — skipping" ansible.builtin.debug: msg: >- No drain state files found for {{ current_node }} in {{ restore_state_dir }}. Skipping restore. when: restore_found_files.files | length == 0 - name: "Restore | {{ current_node }} | End if no state files" ansible.builtin.meta: end_play when: restore_found_files.files | length == 0 - name: "Restore | {{ current_node }} | Use most recent state file" ansible.builtin.set_fact: restore_state_file: >- {{ (restore_found_files.files | sort(attribute='mtime') | last).path }} delegate_to: localhost - name: "Restore | {{ current_node }} | Load state file" ansible.builtin.slurp: src: "{{ restore_state_file }}" register: restore_state_raw delegate_to: localhost - name: "Restore | {{ current_node }} | Parse VM origin list" ansible.builtin.set_fact: restore_vm_list: "{{ restore_state_raw.content | b64decode | from_json }}" delegate_to: localhost - name: "Restore | {{ current_node }} | Log restore plan" ansible.builtin.debug: msg: >- Restoring {{ restore_vm_list | length }} guest(s) to {{ current_node }}: {{ restore_vm_list | map(attribute='vmid') | list }} # ── Get current VM locations ────────────────────────────────────────────────── - name: "Restore | {{ current_node }} | Get current VM locations" community.proxmox.proxmox_vm_info: api_host: "{{ api_host }}" api_user: "{{ api_user }}" api_token_id: "{{ api_token_id }}" api_token_secret: "{{ api_token_secret }}" api_port: "{{ api_port }}" validate_certs: "{{ validate_certs }}" register: restore_all_vms delegate_to: localhost # ── Migrate KVM guests back ─────────────────────────────────────────────────── - name: "Restore | {{ current_node }} | KVM | Migrate back" ansible.builtin.command: > qm migrate {{ item.vmid }} {{ current_node }} {% if item.status == 'running' %}--online{% endif %} --with-local-disks 0 loop: "{{ restore_vm_list | selectattr('type', 'equalto', 'qemu') | list }}" loop_control: label: "{{ item.name }} (VMID {{ item.vmid }})" changed_when: true vars: current_location: >- {{ restore_all_vms.proxmox_vms | selectattr('vmid', 'equalto', item.vmid) | map(attribute='node') | first | default('unknown') }} when: current_location != current_node # ── Migrate LXC guests back ─────────────────────────────────────────────────── - name: "Restore | {{ current_node }} | LXC | Migrate back" ansible.builtin.command: > pct migrate {{ item.vmid }} {{ current_node }} --restart --timeout 120 loop: "{{ restore_vm_list | selectattr('type', 'equalto', 'lxc') | list }}" loop_control: label: "{{ item.name | default(item.vmid) }} (VMID {{ item.vmid }})" changed_when: true vars: current_location: >- {{ restore_all_vms.proxmox_vms | selectattr('vmid', 'equalto', item.vmid) | map(attribute='node') | first | default('unknown') }} when: current_location != current_node # ── Cleanup ─────────────────────────────────────────────────────────────────── - name: "Restore | {{ current_node }} | Remove state file" ansible.builtin.file: path: "{{ restore_state_file }}" state: absent delegate_to: localhost when: restore_cleanup_state_file - name: "Restore | {{ current_node }} | Complete" ansible.builtin.debug: msg: "✓ Restore complete — {{ restore_vm_list | length }} guest(s) returned to {{ current_node }}."