Author SHA1 Message Date
jakub 00b56a7c06 Update mikrotikbackup_clean.yml 2026-09-24 17:55:18 +00:00
jakub 87150f8cd9 Update inv_linuxes 2026-09-07 18:08:46 +00:00
jakub 6c9c003a30 Update inv_linuxes 2026-09-03 16:06:18 +00:00
jakub 0b298372c9 Update inv_linuxes 2026-09-03 14:30:58 +00:00
jakub 07ddce73fb Update inv_linuxes 2026-09-03 14:04:10 +00:00
jakub 600f9ec292 Update inv_linuxes 2026-09-03 13:51:33 +00:00
jakub 64b50f7501 support Arch Linux system updates 2026-09-03 14:58:24 +02:00
jakub b3d706721c Update update.yml 2026-09-02 21:55:23 +00:00
jakub 334fdaec8c Update update.yml 2026-09-02 21:45:22 +00:00
jakub e3def666c8 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-09-02 20:03:52 +00:00
jakub 668e853cba Update initial_install/roles/freeipa_client/tasks/main.yml 2026-09-02 20:01:27 +00:00
jakub 84b0a463c3 Track Portainer STS releases 2026-08-30 01:58:38 +02:00
jakub 928c429568 Fix Portainer updater command 2026-08-30 01:51:47 +02:00
jakub a997469d6e Use official Portainer updater 2026-08-30 01:49:04 +02:00
jakub 075353d90c Update Portainer across release channels 2026-08-30 01:43:10 +02:00
jakub c22e580d3d Add tagged Portainer update role 2026-08-30 01:01:28 +02:00
jakub e66941b147 Update mikrotikbackup_clean.yml 2026-08-24 20:03:30 +00:00
jakub 82ad37e7ae Update initial_install/roles/freeipa_client/tasks/main.yml 2026-07-24 14:18:01 +00:00
jakub 905e3f597c Update initial_install/roles/freeipa_client/tasks/main.yml 2026-07-24 10:54:33 +00:00
jakub 704eff293a Update inv_linuxes 2026-07-24 10:49:14 +00:00
jakub 04cbc15b8e Update inv_linuxes 2026-07-24 10:41:40 +00:00
jakub d91c30c509 Update inv_linuxes 2026-07-24 10:38:38 +00:00
jakub 4e762abd6f Update inv_linuxes 2026-07-24 10:21:18 +00:00
jakub 40d7560e44 Merge pull request 'Manage own borgmatic systemd service and timer' (#1) from backup-systemd-units into main
Reviewed-on: #1
2026-05-30 18:55:15 +00:00
9 changed files with 411 additions and 163 deletions
@@ -11,13 +11,18 @@
- name: Set hostname FQDN
ansible.builtin.hostname:
name: "{{ inventory_hostname }}.im.lab"
name: "{{ inventory_hostname }}"
- name: Check if FreeIPA client is already configured
ansible.builtin.stat:
path: /etc/ipa/default.conf
register: ipa_client_conf
- name: Check if Kerberos host keytab exists
ansible.builtin.stat:
path: /etc/krb5.keytab
register: ipa_keytab
- name: Enroll to FreeIPA
ansible.builtin.command:
argv:
@@ -25,14 +30,36 @@
- --domain=im.lab
- --realm=IM.LAB
- --server=ipa.im.lab
- "--hostname={{ inventory_hostname }}.im.lab"
- "--hostname={{ inventory_hostname }}"
- --mkhomedir
- --principal=admin
- --password={{ ipa_admin_password }}
- "--password={{ ipa_admin_password }}"
- --unattended
- --force-join
no_log: false
when: not ipa_client_conf.stat.exists
no_log: true
when:
- not ipa_client_conf.stat.exists or not ipa_keytab.stat.exists
- name: Check current sudo alternative
ansible.builtin.command:
cmd: update-alternatives --query sudo
register: sudo_alternative
changed_when: false
failed_when: false
when: ansible_facts['distribution'] == "Ubuntu"
- name: Switch to classic sudo
ansible.builtin.command:
argv:
- update-alternatives
- --set
- sudo
- /usr/bin/sudo.ws
when:
- ansible_facts['distribution'] == "Ubuntu"
- sudo_alternative.rc == 0
- "'/usr/bin/sudo.ws' in sudo_alternative.stdout"
- "'Value: /usr/bin/sudo.ws' not in sudo_alternative.stdout"
- name: Prioritize SSS over local accounts in NSS
ansible.builtin.lineinfile:
@@ -44,7 +71,14 @@
- group
notify: Restart SSSD
- name: Enable mkhomedir
- name: Check if authselect is available
ansible.builtin.command:
cmd: command -v authselect
register: authselect_available
changed_when: false
failed_when: false
- name: Enable mkhomedir with authselect
ansible.builtin.command:
argv:
- authselect
@@ -53,6 +87,7 @@
register: authselect_mkhomedir
changed_when: "'already enabled' not in authselect_mkhomedir.stdout"
failed_when: false
when: authselect_available.rc == 0
- name: Enable and start oddjobd
ansible.builtin.service:
+10 -6
View File
@@ -1,20 +1,24 @@
[linux_servers_jim]
jimbuntu ansible_host=192.168.19.4
portainernode2_jim ansible_host=192.168.19.8
galera3 ansible_host=192.168.19.92
galera2 ansible_host=192.168.19.91
portainer2-jim.im.lab ansible_host=192.168.19.8
#galera3 ansible_host=192.168.19.92
#galera2 ansible_host=192.168.19.91
testipaclient ansible_host=192.168.19.98
testclient ansible_host=192.168.19.115
#testclient ansible_host=192.168.19.115
portainer1-jim.im.lab ansible_host=192.168.19.7
[linux_servers_hellsos]
portainer2_hellsos ansible_host=192.168.52.9
portainernode_hellsos ansible_host=192.168.52.21
portainer2-hellsos.im.lab ansible_host=192.168.52.9
portainer1-hellsos.im.lab ansible_host=192.168.52.21
amp-hellsos.im.lab ansible_host=192.168.52.4
[linux_servers:children]
linux_servers_jim
linux_servers_hellsos
[linux_servers_poli]
portainer1-poli.im.lab ansible_host=192.168.6.3
[local]
localhost ansible_connection=local
+154 -105
View File
@@ -1,146 +1,195 @@
---
- name: Backup and/or Upgrade MikroTik
hosts: mikrotik_routers
gather_facts: no
gather_facts: false
vars:
backup_dir: /opt/mikrotik_backups/
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: community.routeros.routeros
ansible_connect_timeout: 60
ansible_command_timeout: 60
upgrade_command_timeout: 600
upgrade_reconnect_timeout: 600
upgrade_verify_retries: 40
upgrade_verify_delay: 15
tasks:
# ----------------------------
# Always: identity + timestamp
# ----------------------------
- name: Get router identity
community.routeros.command:
commands: /system identity print
register: identity_raw
changed_when: false
tags: always
- name: Parse router name
set_fact:
router_name: "{{ identity_raw.stdout[0].split(': ')[1] | trim }}"
ansible.builtin.set_fact:
router_name: "{{ identity_raw.stdout[0].split(': ', 1)[1] | trim }}"
tags: always
- name: Get timestamp
ansible.builtin.command: date +%Y-%m-%d_%H-%M-%S
register: date_out
delegate_to: localhost
vars:
ansible_connection: local
changed_when: false
tags: always
- name: Set timestamp fact
set_fact:
ansible.builtin.set_fact:
ts: "{{ date_out.stdout }}"
tags: always
# ----------------------------
# Backup (tag: backup)
# ----------------------------
- name: Ensure local backup directory exists
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Back up router configuration
tags: [backup, never]
block:
- name: Ensure local backup directory exists
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
vars:
ansible_connection: local
- name: Export router config
community.routeros.command:
commands: /export terse show-sensitive
register: export_cfg
tags: [backup, never]
- name: Export router config
community.routeros.command:
commands: /export terse show-sensitive
register: export_cfg
changed_when: false
no_log: true
- name: Save export locally
ansible.builtin.copy:
content: "{{ export_cfg.stdout[0] }}"
dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc"
delegate_to: localhost
tags: [backup, never]
- name: Save export locally
ansible.builtin.copy:
content: "{{ export_cfg.stdout[0] }}"
dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc"
mode: "0600"
delegate_to: localhost
vars:
ansible_connection: local
no_log: true
# ----------------------------
# Upgrade (tag: upgrade)
# ----------------------------
- name: Check current and latest available package versions
community.routeros.command:
commands: /system package update check-for-updates
register: update_check
- name: Check and upgrade RouterOS packages
tags: [upgrade, never]
when: not ansible_check_mode
block:
- name: Check current and latest available package versions
community.routeros.command:
commands: /system package update check-for-updates
register: update_check
changed_when: false
- name: Normalize update output
set_fact:
_update_text: "{{ update_check.stdout[0] | replace('\r', '') }}"
tags: [upgrade, never]
- name: Normalize update output
ansible.builtin.set_fact:
_update_text: "{{ update_check.stdout[0] | replace('\r', '') }}"
# ⬇️ Add this to see exactly what RouterOS returns before parsing
- name: Debug raw update output
ansible.builtin.debug:
msg: "{{ _update_text }}"
tags: [upgrade, never]
- name: Parse installed and latest versions
ansible.builtin.set_fact:
installed_version: >-
{{ (_update_text | regex_findall('(?:installed|current)-version:[ \t]*([0-9][0-9A-Za-z.-]*)')
| first) | default('unknown') }}
latest_version: >-
{{ (_update_text | regex_findall('(?:latest|newest)-version:[ \t]*([0-9][0-9A-Za-z.-]*)')
| first) | default('unknown') }}
- name: Parse installed and latest versions
set_fact:
installed_version: >-
{{
(_update_text | regex_findall('(?:installed|current)-version:[ ]*([0-9A-Za-z.]+)'))[0]
if (_update_text | regex_findall('(?:installed|current)-version:[ ]*([0-9A-Za-z.]+)'))
else 'unknown'
}}
latest_version: >-
{{
(_update_text | regex_findall('(?:latest|newest)-version:[ ]*([0-9A-Za-z.]+)'))[0]
if (_update_text | regex_findall('(?:latest|newest)-version:[ ]*([0-9A-Za-z.]+)'))
else 'unknown'
}}
tags: [upgrade, never]
- name: Require valid versions before attempting an upgrade
ansible.builtin.assert:
that:
- installed_version != 'unknown'
- latest_version != 'unknown'
fail_msg: "Could not parse versions from update output: {{ _update_text }}"
- name: Fail if versions could not be parsed
ansible.builtin.fail:
msg: >
Could not parse versions from update output.
Raw text was: {{ _update_text }}
when: installed_version == 'unknown' or latest_version == 'unknown'
tags: [upgrade, never]
- name: Show installed and available versions
ansible.builtin.debug:
msg: "{{ router_name }}: installed={{ installed_version }}, available={{ latest_version }}"
- name: Debug parsed versions
ansible.builtin.debug:
msg:
- "Installed: {{ installed_version }}"
- "Latest: {{ latest_version }}"
tags: [upgrade, never]
- name: Skip upgrade if already on latest
ansible.builtin.debug:
msg: "{{ router_name }} is already on {{ installed_version }}. Skipping upgrade."
when: installed_version == latest_version
- name: Skip upgrade if already on latest
ansible.builtin.debug:
msg: "Router {{ router_name }} is already on latest version {{ installed_version }}. Skipping upgrade."
when: installed_version == latest_version
tags: [upgrade, never]
- name: Install and verify the available version
when: installed_version != latest_version
block:
- name: Reset persistent connection before installation
ansible.builtin.meta: reset_connection
- name: Trigger package download and install
community.routeros.command:
commands: /system package update install
register: upgrade_result
when: installed_version != latest_version
tags: [upgrade, never]
- name: Request package installation
block:
- name: Trigger package download and install
community.routeros.command:
commands: /system package update install
vars:
ansible_command_timeout: "{{ upgrade_command_timeout }}"
register: install_result
- name: Wait for router to come back online after reboot
community.routeros.command:
commands: /system resource print
register: reboot_wait
until: reboot_wait is not failed
retries: 30
delay: 15
when:
- installed_version != latest_version
- upgrade_result is not failed
tags: [upgrade, never]
rescue:
# A reboot can interrupt the command response. This is NOT
# success: the mandatory version check below decides the result.
- name: Record installation error for final verification
ansible.builtin.debug:
msg: >-
Installation returned an error: {{ install_result.msg | default('Unknown error') }}.
The command may or may not have reached the router.
Continuing only to verify the actual installed version.
- name: Confirm upgraded version
community.routeros.command:
commands: /system resource print
register: post_upgrade_info
when: installed_version != latest_version
tags: [upgrade, never]
always:
- name: Discard the installation connection
ansible.builtin.meta: reset_connection
- name: Show post-upgrade RouterOS version
ansible.builtin.debug:
msg: "{{ post_upgrade_info.stdout[0] | regex_search('version: .+') }}"
when: installed_version != latest_version
tags: [upgrade, never]
# This readiness check does not claim that a reboot occurred.
# The exact version comparison below is the success criterion.
- name: Wait for SSH availability from Semaphore
ansible.builtin.wait_for:
host: >-
{{ hostvars[inventory_hostname].ansible_host
| default(hostvars[inventory_hostname].ansible_ssh_host)
| default(inventory_hostname) }}
port: >-
{{ hostvars[inventory_hostname].ansible_port
| default(hostvars[inventory_hostname].ansible_ssh_port)
| default(22) }}
state: started
search_regex: SSH-
delay: 30
sleep: 5
connect_timeout: 5
timeout: "{{ upgrade_reconnect_timeout }}"
delegate_to: localhost
vars:
ansible_connection: local
changed_when: false
- name: Reset persistent connection before verification
ansible.builtin.meta: reset_connection
- name: Verify the installed version
block:
- name: Wait until the router reports the expected version
community.routeros.command:
commands: /system resource print
register: post_upgrade_info
changed_when: false
until:
- post_upgrade_info is succeeded
- >-
((post_upgrade_info.stdout | default(['']) | first
| regex_findall('(?m)^\s*version:[ \t]*([0-9][0-9A-Za-z.-]*)')
| first) | default('unknown')) == latest_version
retries: "{{ upgrade_verify_retries }}"
delay: "{{ upgrade_verify_delay }}"
rescue:
- name: Fail if the expected RouterOS version could not be verified
ansible.builtin.fail:
msg: >-
Upgrade NOT verified for {{ router_name }}.
Previous={{ installed_version }}; expected={{ latest_version }}.
Last router response={{ post_upgrade_info.stdout | default([]) }}.
Verification error={{ post_upgrade_info.msg | default('Version mismatch') }}.
Install error={{ install_result.msg | default('No module error reported') }}.
- name: Report verified upgrade
ansible.builtin.debug:
msg: "{{ router_name }}: verified RouterOS {{ installed_version }} -> {{ latest_version }}."
+2 -1
View File
@@ -1,2 +1,3 @@
collections:
- name: community.routeros
- name: community.routeros
- name: community.general
+23
View File
@@ -0,0 +1,23 @@
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
lock_timeout: 300
- name: Perform full upgrade
ansible.builtin.apt:
upgrade: full
autoremove: true
autoclean: true
lock_timeout: 300
register: apt_upgrade
retries: 3
delay: 10
until: apt_upgrade is succeeded
- name: Fix broken packages
ansible.builtin.command:
cmd: apt-get -f install -y
register: fix_result
failed_when: false
changed_when: "'Setting up' in fix_result.stdout"
+9
View File
@@ -0,0 +1,9 @@
---
- name: Update pacman cache and upgrade all packages
community.general.pacman:
update_cache: true
upgrade: true
register: pacman_upgrade
retries: 3
delay: 10
until: pacman_upgrade is succeeded
+5
View File
@@ -0,0 +1,5 @@
---
# Portainer's official updater recreates the server container while preserving
# its existing Docker configuration and persistent data.
portainer_updater_image: portainer/portainer-updater:latest
portainer_target_tag: sts
+72
View File
@@ -0,0 +1,72 @@
---
- name: Check whether Docker is available
ansible.builtin.command:
argv:
- docker
- info
register: portainer_docker_info
changed_when: false
failed_when: false
- name: Find and update Portainer
when: portainer_docker_info.rc == 0
block:
- name: List all container IDs
ansible.builtin.command:
argv:
- docker
- container
- ls
- --all
- --quiet
register: portainer_all_containers
changed_when: false
- name: Inspect all container images
ansible.builtin.command:
argv: >-
{{ ['docker', 'container', 'inspect']
+ portainer_all_containers.stdout_lines }}
register: portainer_container_inspection
changed_when: false
when: portainer_all_containers.stdout_lines | length > 0
- name: Select installed Portainer editions
ansible.builtin.set_fact:
portainer_installed_images: >-
{{ (portainer_container_inspection.stdout | default('[]') | from_json)
| map(attribute='Config.Image')
| select('match',
'^(?:[^/]+/)?portainer/(?:portainer|portainer-(?:ce|ee))(?::|@|$)')
| unique
| list }}
- name: Update Portainer to the newest release
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --pull
- always
- --volume
- /var/run/docker.sock:/var/run/docker.sock
- "{{ portainer_updater_image }}"
- portainer
- >-
--image={{ 'portainer/portainer-ee:' ~ portainer_target_tag
if 'portainer-ee' in item
else 'portainer/portainer-ce:' ~ portainer_target_tag }}
loop: "{{ portainer_installed_images }}"
loop_control:
label: "{{ item }}"
register: portainer_update
changed_when: true
when:
- portainer_installed_images | length > 0
- not ansible_check_mode
- name: Report when no Portainer server is installed
ansible.builtin.debug:
msg: No Portainer CE or Business Edition server container found; skipping.
when: portainer_installed_images | length == 0
+95 -45
View File
@@ -1,91 +1,141 @@
---
- name: Update system (APT + Flatpak)
hosts: all
become: yes
gather_facts: yes
- name: Update Linux system
hosts: all:!localhost:!portainer2-jim.im.lab
become: true
gather_facts: false
serial: 5
tasks:
# ---------------------------------------------------------
# Connectivity
# ---------------------------------------------------------
- name: Ensure SSH is reachable (skip host if not)
delegate_to: localhost
wait_for:
ansible.builtin.wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: 22
timeout: 5
delegate_to: localhost
register: ssh_check
ignore_errors: yes
ignore_errors: true
become: false
- meta: end_host
- name: Skip host if SSH is unreachable
ansible.builtin.meta: end_host
when: ssh_check is failed
- name: Gather facts
ansible.builtin.setup:
- name: Ping with retries (handle intermittent flaps)
ping:
ansible.builtin.ping:
register: ping_result
retries: 5
delay: 5
until: ping_result is success
- name: Wait for apt lock to be released
shell: |
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
echo "Waiting for apt lock..."
sleep 5
done
changed_when: false
# ---------------------------------------------------------
# Distribution packages
# ---------------------------------------------------------
- name: Update apt cache
apt:
update_cache: yes
- name: Update Debian-family packages
ansible.builtin.include_role:
name: update_apt
when: ansible_facts.os_family == 'Debian'
- name: Perform full upgrade
apt:
upgrade: full
autoremove: yes
autoclean: yes
register: apt_upgrade
retries: 3
delay: 10
until: apt_upgrade is succeeded
- name: Update Arch Linux packages
ansible.builtin.include_role:
name: update_arch
when: ansible_facts.os_family == 'Archlinux'
- name: Fix broken packages
command: apt-get -f install -y
register: fix_result
failed_when: false
changed_when: "'Setting up' in fix_result.stdout"
- name: Skip unsupported Linux distribution
ansible.builtin.debug:
msg: >-
Package updates are not configured for
{{ ansible_facts.distribution }} ({{ ansible_facts.os_family }}).
when: ansible_facts.os_family not in ['Debian', 'Archlinux']
# ---------------------------------------------------------
# Flatpak
# ---------------------------------------------------------
- name: Check if Flatpak is installed
command: which flatpak
ansible.builtin.command:
cmd: which flatpak
register: flatpak_check
changed_when: false
failed_when: false
- name: Update system Flatpaks
command: flatpak update -y --noninteractive --system
when: flatpak_check.rc == 0
ansible.builtin.command:
cmd: flatpak update -y --noninteractive --system
register: flatpak_system_update
changed_when: "'Nothing to do' not in flatpak_system_update.stdout"
failed_when: false
when: flatpak_check.rc == 0
- name: Update user Flatpaks
command: flatpak update -y --noninteractive --user
ansible.builtin.command:
cmd: flatpak update -y --noninteractive --user
become: false
when: flatpak_check.rc == 0
register: flatpak_user_update
changed_when: "'Nothing to do' not in flatpak_user_update.stdout"
failed_when: false
when: flatpak_check.rc == 0
- name: Remove unused Flatpaks
command: flatpak uninstall -y --noninteractive --unused
ansible.builtin.command:
cmd: flatpak uninstall -y --noninteractive --unused
register: flatpak_unused
changed_when: "'Nothing unused to uninstall' not in flatpak_unused.stdout"
failed_when: false
when: flatpak_check.rc == 0
# ---------------------------------------------------------
# Snap
# ---------------------------------------------------------
- name: Check if Snap is installed
ansible.builtin.command:
cmd: which snap
register: snap_check
changed_when: false
failed_when: false
- name: Update snap packages
command: snap refresh
ansible.builtin.command:
cmd: snap refresh
register: snap_refresh
changed_when: "'All snaps up to date' not in snap_refresh.stdout"
failed_when: false
when: snap_check.rc == 0
# ---------------------------------------------------------
# Reboot check
# ---------------------------------------------------------
- name: Check if reboot is required
stat:
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Notify if reboot required
debug:
ansible.builtin.debug:
msg: "Reboot required on {{ inventory_hostname }}"
when: reboot_required.stat.exists
when: reboot_required.stat.exists
# ---------------------------------------------------------
# Portainer
# Only runs when --tags portainer is specified
# ---------------------------------------------------------
- name: Update Portainer containers
ansible.builtin.include_role:
name: update_portainer
apply:
tags:
- portainer
tags:
- never
- portainer