diff --git a/mikrotikbackup.yml b/mikrotikbackup.yml index 4da6275..7e66667 100644 --- a/mikrotikbackup.yml +++ b/mikrotikbackup.yml @@ -1,59 +1,127 @@ -- name: Backup MikroTik config (text export only) +# mikrotikbackup.yml + + +- name: Backup MikroTik configuration hosts: mikrotiks - gather_facts: no + gather_facts: false vars: - backup_dir: /opt/mikrotik_backups/ + # This path is located on the Semaphore runner + backup_dir: /opt/mikrotik_backups tasks: - - # ---------------------------- - # Ensure local backup directory - # ---------------------------- - name: Ensure local backup directory exists ansible.builtin.file: path: "{{ backup_dir }}" state: directory - mode: "0755" + mode: "0700" + delegate_to: localhost + run_once: true + + - name: Generate backup timestamp + ansible.builtin.command: + argv: + - date + - --utc + - "+%Y-%m-%d_%H-%M-%S" + register: backup_timestamp + changed_when: false delegate_to: localhost - # ---------------------------- - # Get router identity - # ---------------------------- - name: Get router identity community.routeros.command: - commands: /system identity print - register: identity_raw + commands: + - /system identity print + register: identity_result + changed_when: false - - name: Parse router name - set_fact: - router_name: "{{ identity_raw.stdout[0].split(': ')[1] | trim }}" + - name: Extract router identity line + ansible.builtin.set_fact: + router_identity_line: >- + {{ + ( + identity_result.stdout_lines[0] + | select('match', '^\s*name\s*:') + | list + | first + ) + | default('', true) + }} - # ---------------------------- - # Timestamp - # ---------------------------- - - name: Get timestamp - ansible.builtin.command: date +%Y-%m-%d_%H-%M-%S - register: date_out - delegate_to: localhost + - name: Validate router identity output + ansible.builtin.assert: + that: + - router_identity_line | length > 0 + fail_msg: >- + Could not parse RouterOS identity on + {{ inventory_hostname }}. + quiet: true - - name: Set timestamp fact - set_fact: - ts: "{{ date_out.stdout }}" + - name: Build safe router name + ansible.builtin.set_fact: + router_name: >- + {{ + router_identity_line + | regex_replace('^\s*name\s*:\s*', '') + | trim + | regex_replace('[^A-Za-z0-9._-]+', '_') + | regex_replace('^_+|_+$', '') + }} - # ---------------------------- - # Export config (stable for diff) - # ---------------------------- - - name: Export router config + - name: Validate safe router name + ansible.builtin.assert: + that: + - router_name | length > 0 + fail_msg: >- + Router identity on {{ inventory_hostname }} + cannot be converted to a safe filename. + quiet: true + + - name: Set local backup filename + ansible.builtin.set_fact: + backup_file: >- + {{ + backup_dir + ~ '/' + ~ router_name + ~ '-' + ~ backup_timestamp.stdout + ~ '.rsc' + }} + + - name: Export RouterOS configuration community.routeros.command: - commands: /export terse show-sensitive - register: export_cfg + commands: + - /export terse show-sensitive + register: export_result + changed_when: false + no_log: true - # ---------------------------- - # Save export locally - # ---------------------------- - - name: Save export locally + - name: Validate exported configuration + ansible.builtin.assert: + that: + - export_result.stdout is defined + - export_result.stdout | length > 0 + - export_result.stdout[0] | default('') | trim | length > 0 + fail_msg: >- + RouterOS returned an empty configuration export + for {{ inventory_hostname }}. + quiet: true + no_log: true + + - name: Save RouterOS configuration locally ansible.builtin.copy: - content: "{{ export_cfg.stdout[0] }}" - dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc" + content: "{{ export_result.stdout[0] | trim }}\n" + dest: "{{ backup_file }}" + mode: "0600" delegate_to: localhost + no_log: true + diff: false + + - name: Show backup result + ansible.builtin.debug: + msg: >- + MikroTik backup completed: + host={{ inventory_hostname }} + identity={{ router_name }} + file={{ backup_file }} \ No newline at end of file