From 9f929ac5ee446ae0b39a7d2c1c70ce46f3ffdacd Mon Sep 17 00:00:00 2001 From: "Ben D." Date: Fri, 13 Mar 2026 16:19:28 -0700 Subject: [PATCH] Updated to work around symlink issues --- roles/hypervisor_backup_config/tasks/main.yml | 81 +++++++++++++------ 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/roles/hypervisor_backup_config/tasks/main.yml b/roles/hypervisor_backup_config/tasks/main.yml index f833f7b..f39fbb3 100644 --- a/roles/hypervisor_backup_config/tasks/main.yml +++ b/roles/hypervisor_backup_config/tasks/main.yml @@ -2,6 +2,12 @@ # ============================================================================= # hypervisor_backup_config — main tasks # ============================================================================= +# Strategy: +# - Create a tarball on the remote node (handles symlinks, permissions cleanly) +# - Fetch the tarball to Semaphore host +# - For git: extract into repo path, add/commit/push, clean up +# - For local: leave tarball on the node, rotate old ones +# - For sftp: transfer tarball to remote, clean up temp - name: Gather date/time facts ansible.builtin.setup: @@ -25,25 +31,49 @@ msg: "hypervisor_type '{{ hypervisor_type }}' is not supported. Use proxmox or xcpng." when: hypervisor_type not in ['proxmox', 'xcpng'] +# ── Create tarball on remote node ───────────────────────────────────────────── +# Done once — reused by all destinations +- name: Create config tarball on node + ansible.builtin.shell: | + tar czf /tmp/{{ pve_config_backup_filename }}.tar.gz \ + --ignore-failed-read \ + --dereference \ + {{ pve_config_backup_paths | join(' ') }} 2>/dev/null || true + echo "done" + register: node_tarball + changed_when: true + # ── Git backup ──────────────────────────────────────────────────────────────── - name: Git backup when: "'git' in (pve_config_backup_destinations | map(attribute='type') | list)" block: - - name: Git | Ensure git base path exists on Semaphore host + - name: Git | Ensure extract path exists on Semaphore host ansible.builtin.file: path: "{{ pve_config_git_repo_dir }}/{{ pve_config_git_base_path }}" state: directory mode: '0755' delegate_to: localhost - - name: Git | Copy config files from node to repo + - name: Git | Fetch tarball from node to Semaphore host ansible.builtin.fetch: - src: "{{ item }}" - dest: "{{ pve_config_git_repo_dir }}/{{ pve_config_git_base_path }}/" - flat: false - fail_on_missing: false - loop: "{{ pve_config_backup_paths }}" - ignore_errors: true + src: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + dest: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + flat: true + + - name: Git | Extract tarball into repo path + ansible.builtin.shell: | + tar xzf /tmp/{{ pve_config_backup_filename }}.tar.gz \ + -C {{ pve_config_git_repo_dir }}/{{ pve_config_git_base_path }} \ + --strip-components=0 2>/dev/null || true + echo "extracted" + delegate_to: localhost + changed_when: true + + - name: Git | Clean up local temp tarball + ansible.builtin.file: + path: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + state: absent + delegate_to: localhost - name: Git | Check if there are changes to commit ansible.builtin.shell: | @@ -84,20 +114,16 @@ - name: Local backup when: "'local' in (pve_config_backup_destinations | map(attribute='type') | list)" block: - - name: Local | Ensure backup dir exists + - name: Local | Ensure backup dir exists on node ansible.builtin.file: path: "{{ pve_config_local_backup_dir }}" state: directory mode: '0700' - - name: Local | Create gzipped tarball of config paths + - name: Local | Move tarball to backup dir ansible.builtin.shell: | - tar czf {{ pve_config_local_backup_dir }}/{{ pve_config_backup_filename }}.tar.gz \ - --ignore-failed-read \ - {% for path in pve_config_backup_paths %}{{ path }} {% endfor %} - - echo "Created: {{ pve_config_backup_filename }}.tar.gz" - register: local_backup_result + cp /tmp/{{ pve_config_backup_filename }}.tar.gz \ + {{ pve_config_local_backup_dir }}/{{ pve_config_backup_filename }}.tar.gz changed_when: true - name: Local | Remove old backups beyond keep limit @@ -105,7 +131,6 @@ ls -1t {{ pve_config_local_backup_dir }}/{{ hypervisor_type }}_{{ client_id | lower | replace('-','_') | replace(' ','_') }}_{{ inventory_hostname }}_config_*.tar.gz 2>/dev/null \ | tail -n +{{ (pve_config_backup_keep | int) + 1 }} \ | xargs -r rm -f - echo "Rotation complete" changed_when: false - name: Local | Log result @@ -118,15 +143,14 @@ block: - name: SFTP | Validate required vars are set ansible.builtin.fail: - msg: "sftp destination requires pve_config_sftp_host, pve_config_sftp_user to be set" + msg: "sftp destination requires pve_config_sftp_host and pve_config_sftp_user to be set" when: pve_config_sftp_host == "" or pve_config_sftp_user == "" - - name: SFTP | Create local temp tarball first - ansible.builtin.shell: | - tar czf /tmp/{{ pve_config_backup_filename }}.tar.gz \ - --ignore-failed-read \ - {% for path in pve_config_backup_paths %}{{ path }} {% endfor %} - changed_when: true + - name: SFTP | Fetch tarball to Semaphore host first + ansible.builtin.fetch: + src: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + dest: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + flat: true - name: SFTP | Transfer tarball to remote host ansible.builtin.shell: | @@ -138,14 +162,21 @@ cd {{ pve_config_sftp_remote_dir }} put /tmp/{{ pve_config_backup_filename }}.tar.gz EOF + delegate_to: localhost changed_when: true - - name: SFTP | Remove temp tarball + - name: SFTP | Clean up local temp tarball ansible.builtin.file: path: "/tmp/{{ pve_config_backup_filename }}.tar.gz" state: absent + delegate_to: localhost - name: SFTP | Log result ansible.builtin.debug: msg: "Config backed up via sftp: {{ pve_config_sftp_host }}:{{ pve_config_sftp_remote_dir }}/{{ pve_config_backup_filename }}.tar.gz" +# ── Clean up temp tarball on node ───────────────────────────────────────────── +- name: Clean up temp tarball on node + ansible.builtin.file: + path: "/tmp/{{ pve_config_backup_filename }}.tar.gz" + state: absent \ No newline at end of file