4
0
forked from jakub/ansible

feat: enhance MikroTik backup playbook with improved validation and structure

This commit is contained in:
martin.fencl
2026-07-29 15:29:17 +02:00
parent 04996b47f8
commit ce3d3d16f9
+267 -85
View File
@@ -1,149 +1,331 @@
# mikrotikbackup_clean.yml # mikrotikbackup_clean.yml
- name: Backup and/or Upgrade MikroTik ---
hosts: mikrotik_routers - name: Backup and/or upgrade MikroTik
gather_facts: no hosts: mikrotiks
gather_facts: false
vars: vars:
backup_dir: /opt/mikrotik_backups/ # This directory is located on the Semaphore runner
backup_dir: /opt/mikrotik_backups
# Maximum time for a router to return after an upgrade
upgrade_wait_timeout: 900
tasks: tasks:
# -------------------------------------------------------------------------
# Common information
# -------------------------------------------------------------------------
# ----------------------------
# Always: identity + timestamp
# ----------------------------
- name: Get router identity - name: Get router identity
community.routeros.command: community.routeros.command:
commands: /system identity print commands:
- /system identity get name
register: identity_raw register: identity_raw
tags: always changed_when: false
tags:
- always
- name: Parse router name - name: Build safe router name
set_fact: ansible.builtin.set_fact:
router_name: "{{ identity_raw.stdout[0].split(': ')[1] | trim }}" router_name: >-
tags: always {{
(
identity_raw.stdout[0]
| default(inventory_hostname, true)
| trim
)
| regex_replace('[^A-Za-z0-9._-]+', '_')
| regex_replace('^_+|_+$', '')
}}
tags:
- always
- name: Get timestamp - name: Validate router name
ansible.builtin.command: date +%Y-%m-%d_%H-%M-%S ansible.builtin.assert:
that:
- router_name | length > 0
fail_msg: >-
Could not determine RouterOS identity for
{{ inventory_hostname }}.
quiet: true
tags:
- always
- name: Generate backup timestamp
ansible.builtin.command:
argv:
- date
- --utc
- "+%Y-%m-%d_%H-%M-%S"
register: date_out register: date_out
changed_when: false
delegate_to: localhost delegate_to: localhost
tags: always tags:
- always
- name: Set timestamp fact - name: Set timestamp fact
set_fact: ansible.builtin.set_fact:
ts: "{{ date_out.stdout }}" backup_timestamp: "{{ date_out.stdout | trim }}"
tags: always tags:
- always
# -------------------------------------------------------------------------
# Backup
# Run with: --tags backup
# -------------------------------------------------------------------------
# ----------------------------
# Backup (tag: backup)
# ----------------------------
- name: Ensure local backup directory exists - name: Ensure local backup directory exists
ansible.builtin.file: ansible.builtin.file:
path: "{{ backup_dir }}" path: "{{ backup_dir }}"
state: directory state: directory
mode: "0755" mode: "0700"
delegate_to: localhost delegate_to: localhost
tags: [backup, never] run_once: true
tags:
- backup
- never
- name: Export router config - name: Export RouterOS configuration
community.routeros.command: community.routeros.command:
commands: /export terse show-sensitive commands:
- /export terse show-sensitive
register: export_cfg register: export_cfg
tags: [backup, never] changed_when: false
no_log: true
tags:
- backup
- never
- name: Save export locally - name: Validate exported configuration
ansible.builtin.assert:
that:
- export_cfg.stdout is defined
- export_cfg.stdout | length > 0
- export_cfg.stdout[0] | default('') | trim | length > 0
fail_msg: >-
RouterOS returned an empty configuration export
for {{ inventory_hostname }}.
quiet: true
no_log: true
tags:
- backup
- never
- name: Set backup filename
ansible.builtin.set_fact:
backup_file: >-
{{
backup_dir
~ '/'
~ router_name
~ '-'
~ backup_timestamp
~ '.rsc'
}}
tags:
- backup
- never
- name: Save RouterOS configuration locally
ansible.builtin.copy: ansible.builtin.copy:
content: "{{ export_cfg.stdout[0] }}" content: "{{ export_cfg.stdout[0] | trim }}\n"
dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc" dest: "{{ backup_file }}"
mode: "0600"
delegate_to: localhost delegate_to: localhost
tags: [backup, never] no_log: true
diff: false
tags:
- backup
- never
# ---------------------------- - name: Show backup result
# Upgrade (tag: upgrade) ansible.builtin.debug:
# ---------------------------- msg: >-
- name: Check current and latest available package versions MikroTik backup completed:
host={{ inventory_hostname }}
identity={{ router_name }}
file={{ backup_file }}
tags:
- backup
- never
# -------------------------------------------------------------------------
# Upgrade
# Run with: --tags upgrade
# Run with backup before upgrade: --tags backup,upgrade
# -------------------------------------------------------------------------
- name: Check current and latest RouterOS versions
community.routeros.command: community.routeros.command:
commands: /system package update check-for-updates commands:
- /system package update check-for-updates
register: update_check register: update_check
tags: [upgrade, never] changed_when: false
tags:
- upgrade
- never
- name: Normalize update output - name: Normalize update output
set_fact: ansible.builtin.set_fact:
_update_text: "{{ update_check.stdout[0] | replace('\r', '') }}" update_text: >-
tags: [upgrade, never] {{
update_check.stdout[0]
| default('')
| replace('\r', '')
}}
tags:
- upgrade
- never
# ⬇️ Add this to see exactly what RouterOS returns before parsing - name: Extract version matches
- name: Debug raw update output ansible.builtin.set_fact:
ansible.builtin.debug: installed_version_matches: >-
msg: "{{ _update_text }}" {{
tags: [upgrade, never] update_text
| regex_findall(
'(?im)^\\s*(?:installed|current)-version:\\s*(\\S+)\\s*$'
)
}}
latest_version_matches: >-
{{
update_text
| regex_findall(
'(?im)^\\s*(?:latest|newest)-version:\\s*(\\S+)\\s*$'
)
}}
tags:
- upgrade
- never
- name: Parse installed and latest versions - name: Set installed and latest versions
set_fact: ansible.builtin.set_fact:
installed_version: >- installed_version: >-
{{ {{
(_update_text | regex_findall('(?:installed|current)-version:[ ]*([0-9A-Za-z.]+)'))[0] installed_version_matches[0]
if (_update_text | regex_findall('(?:installed|current)-version:[ ]*([0-9A-Za-z.]+)')) if installed_version_matches | length > 0
else 'unknown' else 'unknown'
}} }}
latest_version: >- latest_version: >-
{{ {{
(_update_text | regex_findall('(?:latest|newest)-version:[ ]*([0-9A-Za-z.]+)'))[0] latest_version_matches[0]
if (_update_text | regex_findall('(?:latest|newest)-version:[ ]*([0-9A-Za-z.]+)')) if latest_version_matches | length > 0
else 'unknown' else 'unknown'
}} }}
tags: [upgrade, never] tags:
- upgrade
- never
- name: Fail if versions could not be parsed - name: Show raw update output
ansible.builtin.fail: ansible.builtin.debug:
msg: > msg: "{{ update_text }}"
Could not parse versions from update output. tags:
Raw text was: {{ _update_text }} - upgrade
when: installed_version == 'unknown' or latest_version == 'unknown' - never
tags: [upgrade, never]
- name: Debug parsed versions - name: Validate parsed RouterOS versions
ansible.builtin.assert:
that:
- installed_version != 'unknown'
- latest_version != 'unknown'
fail_msg: >-
Could not parse RouterOS versions on
{{ inventory_hostname }}.
Raw output: {{ update_text }}
quiet: true
tags:
- upgrade
- never
- name: Show RouterOS versions
ansible.builtin.debug: ansible.builtin.debug:
msg: msg:
- "Router: {{ router_name }}"
- "Installed: {{ installed_version }}" - "Installed: {{ installed_version }}"
- "Latest: {{ latest_version }}" - "Latest: {{ latest_version }}"
tags: [upgrade, never] tags:
- upgrade
- never
- name: Skip upgrade if already on latest - name: Show already up-to-date result
ansible.builtin.debug: ansible.builtin.debug:
msg: "Router {{ router_name }} is already on latest version {{ installed_version }}. Skipping upgrade." msg: >-
Router {{ router_name }} is already running
RouterOS {{ installed_version }}.
when: installed_version == latest_version when: installed_version == latest_version
tags: [upgrade, never] tags:
- upgrade
- never
- name: Trigger package download and install - name: Trigger RouterOS package installation
community.routeros.command: community.routeros.command:
commands: /system package update install commands:
- /system package update install
register: upgrade_result register: upgrade_result
changed_when: true
failed_when: false
when: installed_version != latest_version when: installed_version != latest_version
tags: [upgrade, never] tags:
- upgrade
- never
- name: Wait for router to come back online after reboot - name: Wait for router SSH service to stop
ansible.builtin.wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: "{{ ansible_port | default(22) | int }}"
state: stopped
connect_timeout: 5
timeout: 180
delegate_to: localhost
failed_when: false
when: installed_version != latest_version
tags:
- upgrade
- never
- name: Wait for router SSH service to start
ansible.builtin.wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: "{{ ansible_port | default(22) | int }}"
state: started
delay: 10
connect_timeout: 5
timeout: "{{ upgrade_wait_timeout }}"
delegate_to: localhost
when: installed_version != latest_version
tags:
- upgrade
- never
- name: Reset RouterOS connection
ansible.builtin.meta: reset_connection
when: installed_version != latest_version
tags:
- upgrade
- never
- name: Read RouterOS version after upgrade
community.routeros.command: community.routeros.command:
commands: /system resource print commands:
register: reboot_wait - /system resource get version
until: reboot_wait is not failed register: post_upgrade_version
retries: 30 retries: 20
delay: 15 delay: 15
when: until: post_upgrade_version is succeeded
- installed_version != latest_version changed_when: false
- upgrade_result is not failed
tags: [upgrade, never]
- name: Confirm upgraded version
community.routeros.command:
commands: /system resource print
register: post_upgrade_info
when: installed_version != latest_version when: installed_version != latest_version
tags: [upgrade, never] tags:
- upgrade
- never
- name: Show post-upgrade RouterOS version - name: Show RouterOS version after upgrade
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ post_upgrade_info.stdout[0] | regex_search('version: .+') }}" msg: >-
Router {{ router_name }} is online.
RouterOS version:
{{ post_upgrade_version.stdout[0] | default('unknown') | trim }}
when: installed_version != latest_version when: installed_version != latest_version
tags: [upgrade, never] tags:
- upgrade
- never