--- # This playbook backups the odroid docker containers. - name: Backup odroid hosts: all gather_facts: true tasks: - name: Backup odroid block: - include_vars: vars/common/mattermost.yaml - include_vars: vars/odroid/variable.yaml - name: Check if directories exist stat: path: "{{ backup_dir }}/{{ item }}" register: dirs loop: "{{ systems }}" - name: Create not existing directories file: path: "{{ backup_dir }}/{{ item.item }}" state: directory mode: 0755 group: chris owner: chris when: item.stat.exists == false with_items: "{{ dirs.results }}" loop_control: label: "{{ item.item }}" - name: Backup container volumes shell: docker run --rm -v {{ backup_dir }}/{{ item.value.container }}:/backup --volumes-from {{ item.value.container }} busybox tar cvfz /backup/{{ item.key }}-{{ ansible_date_time.iso8601_basic_short }}.tar.gz {{ item.value.path }} loop: "{{ lookup('dict', volumes) }}" loop_control: label: "{{ item.key }}" - name: Change ownership of volume backups file: path: "{{ backup_dir }}/{{ item.value.container }}/{{ item.key }}-{{ ansible_date_time.iso8601_basic_short }}.tar.gz" owner: chris group: chris become: yes become_method: sudo become_user: root loop: "{{ lookup('dict', volumes) }}" loop_control: label: "{{ item.key }}" - name: Backup mysql databases shell: docker exec {{ item }}-db mysqldump -u{{ item }} -p{{ lookup('vars', item ~ '-password') }} {{ item }} > {{ backup_dir }}/{{ item }}/db-{{ ansible_date_time.iso8601_basic_short }}.sql loop: "{{ mysql_databases }}" - name: Backup postgres databases shell: docker exec {{ item }}-db pg_dump -U {{ item }} {{ item }} > {{ backup_dir }}/{{ item }}/db-{{ ansible_date_time.iso8601_basic_short }}.sql loop: "{{ postgres_databases }}" - name: Compress database backup files shell: gzip {{ backup_dir }}/{{ item }}/db-{{ ansible_date_time.iso8601_basic_short }}.sql loop: "{{ databases }}" - name: Check logrotate directories stat: path: "{{ backup_dir }}/{{ item[0] }}/{{ item[1] }}" register: subdirs with_nested: - "{{ systems }}" - ['daily', 'weekly', 'monthly', 'yearly'] loop_control: label: "{{ item[0] }} {{ item[1] }}" - name: Create not existing logrotate directories file: path: "{{ backup_dir }}/{{ item.item[0] }}/{{ item.item[1] }}" state: directory mode: 0755 group: chris owner: chris when: item.stat.exists == false with_items: "{{ subdirs.results }}" loop_control: label: "{{ item.item[0] }} {{ item.item[1] }}" - name: Search for the created backups find: paths: "{{ backup_dir }}/{{ item }}" file_type: file patterns: '*.gz' recurse: false register: backup_files loop: "{{ systems }}" - name: Store the yearly backups copy: remote_src: true src: "{{ item.path }}" dest: "{{ item.path | dirname }}/yearly/{{ item.path | basename }}" with_items: "{{ backup_files.results | map(attribute='files') | list }}" when: (ansible_date_time.day == "01" and ansible_date_time.month == "01") loop_control: label: "{{ item.path }}" - name: Store the monthly backups copy: remote_src: true src: "{{ item.path }}" dest: "{{ item.path | dirname }}/monthly/{{ item.path | basename }}" with_items: "{{ backup_files.results | map(attribute='files') | list }}" when: (ansible_date_time.day == "01" and ansible_date_time.month != "01") loop_control: label: "{{ item.path }}" - name: Store the weekly backups copy: remote_src: true src: "{{ item.path }}" dest: "{{ item.path | dirname }}/weekly/{{ item.path | basename }}" with_items: "{{ backup_files.results | map(attribute='files') | list }}" when: (ansible_date_time.weekday_number == "1") loop_control: label: "{{ item.path }}" - name: Store the daily backup copy: remote_src: true src: "{{ item.path }}" dest: "{{ item.path | dirname }}/daily/{{ item.path | basename }}" with_items: "{{ backup_files.results | map(attribute='files') | list }}" when: (ansible_date_time.weekday_number != "1" and ansible_date_time.day != "01") loop_control: label: "{{ item.path }}" - name: Cleanup original backup files file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Find old daily backups find: paths: "{{ backup_dir }}/{{ item }}/daily" file_type: file age: 8d age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files loop: "{{ systems }}" - name: Delete old daily backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Find old weekly backups find: paths: "{{ backup_dir }}/{{ item }}/weekly" file_type: file age: 5w age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files loop: "{{ systems }}" - name: Delete old weekly backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Find old monthly backups find: paths: "{{ backup_dir }}/{{ item }}/monthly" file_type: file age: 56w age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files loop: "{{ systems }}" - name: Delete old monthly backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Search for other servers' backup directories find: paths: "/media/backup/{{ item }}" file_type: directory recurse: false register: backup_server_dirs loop: "{{ servers }}" - name: Find servers old daily backups find: path: "{{ item.path }}/daily" file_type: file age: 8d age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files with_items: "{{ backup_server_dirs.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Delete servers old daily backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Find servers old weekly backups find: path: "{{ item.path }}/weekly" file_type: file age: 5w age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files with_items: "{{ backup_server_dirs.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Delete servers old weekly backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Find servers old monthly backups find: path: "{{ item.path }}/monthly" file_type: file age: 56w age_stamp: ctime patterns: '*.gz' recurse: false register: backup_files with_items: "{{ backup_server_dirs.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Delete servers old monthly backups file: path: "{{ item.path }}" state: absent with_items: "{{ backup_files.results | map(attribute='files') | list }}" loop_control: label: "{{ item.path }}" - name: Inform Mattermost about success uri: url: "{{ mattermost_url }}" method: POST body_format: json body: channel_id: "{{ channel_id }}" message: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}: Playbook ran successful ({{ ansible_play_name }})" headers: Content-Type: application/json Authorization: Bearer "{{ semaphore_token }}" status_code: [200, 201] rescue: - name: Inform Mattermost about error uri: url: "{{ mattermost_url }}" method: POST body_format: json body: channel_id: "{{ channel_id }}" message: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}: Playbook ran with error ({{ ansible_play_name }})" headers: Content-Type: application/json Authorization: Bearer "{{ error_token }}" status_code: [200, 201]