86 Commits
Author SHA1 Message Date
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
jakubandClaude Opus 4.8 b84afb3abf Manage own borgmatic systemd service and timer
Ship borgmatic.service and borgmatic.timer from the backup role instead
of relying on the package-provided units. The units are deployed to
/etc/systemd/system (overriding the package units), with a configurable
schedule via role defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:53:27 +02:00
jakubandClaude Opus 4.7 65a02177fa Prioritize SSS over local accounts in nsswitch
Rewrites the passwd and group lines in /etc/nsswitch.conf so SSSD
is consulted before local files, and notifies the existing SSSD
restart handler so the change takes effect immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:02:29 +02:00
jakubandClaude Opus 4.7 9eb3e446af Wire FreeIPA enrolment into setup_linux
Reachable via --tags sssd; reuses the existing freeipa_client role
via relative path from the umbrella playbook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:43:39 +02:00
jakubandClaude Opus 4.7 52bb82f900 Wire backup into setup_linux and add portainer1-jim backup host
Imports backup.yml from setup_linux.yml so the backup play is
reachable from the umbrella playbook via --tags backup. Also adds
a backup_hosts entry for portainer1-jim.im.lab (5GB, /data/compose).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 17:07:29 +02:00
jakubandClaude Opus 4.7 f657767632 Tag baseline user play with 'users'
Lets the user play be targeted with --tags users while still running
by default when no tags are passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 15:45:41 +02:00
jakub 5bcdf66bb5 Update inv_linuxes 2026-05-23 13:18:25 +00:00
jakubandClaude Opus 4.7 8f14ec2e69 Rename hellsos/jim to hellsoslocal/jimlocal
Carry forward the local-account rename from the direct edit on
initial_setup.yml into the new canonical users list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:55:48 +02:00
jakubandClaude Opus 4.7 6f73b83bc0 Centralize users list in group_vars and rename baseline playbook
Move the canonical user list to group_vars/all/users.yml so both
setup_linux.yml (renamed from initial_setup.yml) and the
initial_install users role consume the same source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:55:38 +02:00
jakub b5cb064bd8 Update initial_setup.yml 2026-05-23 12:18:57 +00:00
jakubandClaude Opus 4.7 ef49608ccc Add hostname play and hellsos SSH keys to initial_setup
Adds an opt-in (tags: never,hostname) play that sets the system
hostname to inventory_hostname, and fills in both real authorized
keys for the hellsos user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:13:23 +02:00
jakub a23492d9d1 Update inv_linuxes 2026-05-23 11:28:47 +00:00
jakubandClaude Opus 4.7 b7f4ba6502 Add dockhand role to initial_install
Tagged never,dockhand_install so it only runs when explicitly requested.
Installs docker.io + docker-compose-v2, templates a compose file for
fnsys/dockhand:latest at /docker/dockhand, and wires a oneshot systemd
unit that brings the stack up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:51:14 +02:00
jakubandClaude Opus 4.7 54e111338d Encrypt borg repos with repokey-blake2 + shared passphrase
borg_passphrase is required (Semaphore secret, same across hosts).
The role writes it to /etc/borgmatic/passphrase (0600 root) and
configures borgmatic to use BORG_PASSCOMMAND=cat /etc/borgmatic/passphrase,
and runs `borg init --encryption=repokey-blake2` with BORG_PASSPHRASE in
the env. no_log on the tasks that touch the passphrase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:58:14 +02:00
jakubandClaude Opus 4.7 885a617388 Fix borg SSH URI and auto-init the remote repo
The URI was wrong — BorgWarehouse uses a single shared SSH user
(`borgwarehouse`) and routes by the repo id in the path, so the form
is `ssh://<borgSshHost>/./<repo.id>` (not per-repo user with /./repos).

Role now also trusts the borg server's SSH host key in root's
known_hosts and runs `borg init --encryption=none` (idempotent — treats
"already exists" as success) so first backups don't need manual prep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:45:06 +02:00
jakubandClaude Opus 4.7 0a97f00356 Auto-register borg repo on the controller per host
backup role now logs into borgcontroller and creates (or looks up) a
repository with alias=inventory_hostname, registering root's pubkey and
the requested storageSize. The resulting SSH URI is injected into the
borgmatic config so each host gets a remote-managed repo without manual
config.

backup_hosts entries gain a `storage_size_gb` field (stripped before
templating) and lose the manual `repositories` entry — the role fills it.
borgcontroller_{username,password} are expected from Semaphore secrets.

Also gitignores .claude/ local state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:37:10 +02:00
jakubandClaude Opus 4.7 4275f2e8fe Enable borgmatic timer and generate root SSH key for borg server
The role now ensures the systemd timer is running (so backups actually
fire on the schedule borgmatic ships by default) and generates an
ed25519 key for root that can be authorized on the remote borg server.

Also adds a testipaclient entry to backup_hosts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:49:58 +02:00
jakubandClaude Opus 4.7 0d027a2c73 Add .gitignore for venv and mikrotik backup output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:41:48 +02:00
jakubandClaude Opus 4.7 e43c3aaae3 Add backup role and manage_ssh_keys role
- Borgmatic backup role driven by per-host config in group_vars/all/backup.yml
- manage_ssh_keys role with add/remove paths; remove_ssh_key_playbook.yml uses it

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:38:11 +02:00
jakub f540af580f Update inv_linuxes 2026-05-06 14:40:39 +00:00
jakub 34edead9b4 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 20:44:46 +00:00
jakub 4987003c4e Update initial_install/roles/baseline_sudo/tasks/main.yml 2026-04-24 20:18:37 +00:00
jakub 22413beb25 Update initial_install/roles/users/tasks/main.yml 2026-04-24 20:16:31 +00:00
jakub 04ce966b20 Update initial_install/roles/users/tasks/main.yml 2026-04-24 20:12:19 +00:00
jakub 6368edcb67 Update initial_install/roles/users/tasks/main.yml 2026-04-24 20:09:02 +00:00
jakub 949940c730 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 15:00:33 +00:00
jakub 9cc587cda5 Update initial_install/roles/users/tasks/main.yml 2026-04-24 14:53:48 +00:00
jakub 65dc887749 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 14:51:35 +00:00
jakub e2015fe03e Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 14:48:14 +00:00
jakub ef3b293977 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 14:45:40 +00:00
jakub 9d9695a7b3 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 14:43:37 +00:00
jakub becc21ff9e Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 14:36:32 +00:00
jakub 22deb79b46 Update initial_install/roles/users/tasks/main.yml 2026-04-24 14:30:08 +00:00
jakub 9c2f0e577b Update initial_install/roles/baseline_sudo/tasks/main.yml 2026-04-24 12:20:39 +00:00
jakub 50b4bfa6fc Add initial_install/collections/requirements.yml 2026-04-24 12:09:57 +00:00
jakub 873af66079 Update inv_linuxes 2026-04-24 12:08:29 +00:00
jakub 504dc88756 Add initial_install/roles/freeipa_client/handlers/main.yml 2026-04-24 11:58:18 +00:00
jakub a2393f7f44 Update initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 11:57:32 +00:00
jakub 0583f5c85f Add initial_install/roles/freeipa_client/tasks/main.yml 2026-04-24 11:56:42 +00:00
jakub 7af44add30 Add initial_install/roles/ssh_hardening/handlers/main.yml 2026-04-24 11:56:22 +00:00
jakub ce17a4b00e Add initial_install/roles/ssh_hardening/tasks/main.yml 2026-04-24 11:56:01 +00:00
jakub cc679c5953 Add initial_install/roles/users/tasks/main.yml 2026-04-24 11:55:36 +00:00
jakub 26ae15cd46 Add initial_install/roles/baseline_sudo/tasks/main.yml 2026-04-24 11:54:41 +00:00
jakub e4cabaf2d5 Add initial_install/playbook.yml 2026-04-24 11:54:03 +00:00
jakub c22c566fa2 Update test_sms.yml 2026-04-13 16:50:08 +00:00
jakub 6de6fde4cd Update mikrotikbackup_clean.yml 2026-03-25 10:50:50 +00:00
jakub 5ba549a052 Update mikrotikbackup_clean.yml 2026-03-24 15:11:28 +00:00
jakub 699d7ef089 Update mikrotikbackup_clean.yml 2026-03-24 15:03:43 +00:00
jakub ce1ba9cf97 Update mikrotikbackup_clean.yml 2026-03-24 15:00:40 +00:00
jakub 724e954828 Update mikrotikbackup_clean.yml 2026-03-24 14:59:02 +00:00
jakub b0f5825d8a Update mikrotikbackup_clean.yml 2026-03-24 14:56:42 +00:00
jakub 2779970324 Update mikrotikbackup_clean.yml 2026-03-24 14:54:06 +00:00
jakub 93e8d5abb4 Update mikrotikbackup_clean.yml 2026-03-24 14:52:22 +00:00
jakub 7ab8b46e3e Update mikrotikbackup_clean.yml 2026-03-24 14:50:17 +00:00
jakub 482a298fdb Update mikrotikbackup_clean.yml 2026-03-24 14:46:32 +00:00
jakub 069579aa5a Update mikrotikbackup_clean.yml 2026-03-24 14:43:42 +00:00
jakub c8dbc4c518 Update mikrotikbackup_clean.yml 2026-03-24 14:41:22 +00:00
jakub 33a2d86910 Update mikrotikbackup_clean.yml 2026-03-24 14:39:19 +00:00
jakub 2fa716624e Update mikrotikbackup_clean.yml 2026-03-24 14:35:26 +00:00
jakub d65ac431e5 Update inv_linuxes 2026-03-23 17:11:03 +00:00
jakub 5d17ba7d6b Update initial_setup.yml 2026-03-23 17:09:47 +00:00
jakub daf198ea82 Update initial_setup.yml 2026-03-23 17:08:37 +00:00
jakub 3563c69d54 Update inv_linuxes 2026-03-23 16:25:22 +00:00
jakub b727d51dfd Update mikrotikbackup_clean.yml 2026-03-21 18:45:03 +00:00
36 changed files with 1036 additions and 96 deletions
+10
View File
@@ -0,0 +1,10 @@
# Python virtual environments
.venv/
venv/
# MikroTik backup output
mikrotik/backups/
mikrotik/output/
# Claude Code local state
.claude/
+8
View File
@@ -0,0 +1,8 @@
---
- name: Install borgmatic and deploy per-host config
hosts: all
become: true
tags: never,backup
roles:
- role: backup
+25
View File
@@ -0,0 +1,25 @@
---
# Borg Controller — auto-creates a repo per host on a BorgWarehouse-backed server.
# borgcontroller_username / borgcontroller_password come from Semaphore secrets.
borgcontroller_url: https://borgcontroller.internet-master.cz
# Per-host borgmatic config. Hosts not listed here are skipped by the `backup` role.
# `storage_size_gb` is stripped before rendering and used to size the controller-side
# repo. `repositories` is auto-filled from the controller — don't set it manually.
# Other keys are passed through verbatim to borgmatic, see
# https://torsion.org/borgmatic/docs/reference/configuration/
backup_hosts:
testipaclient:
storage_size_gb: 10
source_directories:
- /home/jakub
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
portainer1-jim.im.lab:
storage_size_gb: 5
source_directories:
- /data/compose
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
+22
View File
@@ -0,0 +1,22 @@
---
# Canonical user list — consumed by both setup_linux.yml and
# initial_install/roles/users.
users:
- name: automation
shell: /bin/bash
sudo_nopasswd: true
ssh_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEx+ltCKNIEM7F4PzGLv22cIu7N0Fpn5gxwV02xq0GS9 automation@internet-master.cz"
- name: hellsoslocal
shell: /bin/bash
sudo_nopasswd: true
ssh_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB+wC3T4/HSs1q5jf34sCqicSQOb05k+bxfmNMMKEGzRrGT3BfCG428F19OBIswvcuBPC0Q4TpPz84BkiATCx2o1JUH1xIOFcHzxxXbyzHAhjwto1wOr1DkwZWAvDPbdnJ39OsC0EdmrAHSXut93q4vzOsLlS34bOWP1THGY9nBKOHwJUQmS5tLw6dqbhKA886TrPXJDR9euEC+SYaytMyDUPYEa6dlDyRp77eII/uI/hf/6e+34wm9XyFyGMiMrQeO0u6Gq5NhsoBlhrCW3ds0To+DBZ/YKNzpzcN+uPKM1+r9nN8KwdwjRkQEwSdB4osz/UeGTiXB0jwb0+ftFthBFdOil86cd1OiAMmuKB/19QHv0NsVhs2JocP5JcrAgx8ktQzLIkOM4lt6Kt9rjHv1KNfdsZdiHqlOwrDv9B2Ei44qEUAsWlFSzEi7R3mOED4F04N3FeQ9TkrRRH6SE733t1Kum2VAz6wr4BSNyYxQOCnXoANy/JyoM5e5tQ7pht7tuxX78zhFlC7pAmVu0dnCQimSsIsXNlYGM7DQ7QMva8mKu49V3B7tU0ChghSRJUb2Sg0tkTAxzCR+WOOf8kAnkXNOV5vExQ3h5Xmb52A+az37Hyex379ryKqpffaf9RTx9pBagQf1XbUtA4KazuEi3fMmqCQjz+xFZJAZQ== hellsos@hellsos-PC"
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKhfQt1VNQo8EbIog4yjU5VEF3mTyMEC7o1Qe95X4JwG jan@rabcan.cz"
- name: jimlocal
shell: /bin/bash
sudo_nopasswd: true
ssh_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPFS4fsqMjMMu/Bi/884bw7yJBqvWusDRESvanH6Owco jakub@jimbuntu"
@@ -0,0 +1,2 @@
collections:
- name: freeipa.ansible_freeipa
+47
View File
@@ -0,0 +1,47 @@
---
- name: Baseline system setup
hosts: all
become: true
roles:
- role: baseline_sudo
tags: sudo
- role: users
tags: users
# ==============================
# FREEIPA / SSSD (optional)
# ==============================
- name: FreeIPA client setup
hosts: all
become: true
tags: never,sssd
roles:
- role: freeipa_client
# ==============================
# DOCKHAND (optional)
# ==============================
- name: Install dockhand
hosts: all
become: true
tags: never,dockhand_install
roles:
- role: dockhand
# ==============================
# SSH HARDENING (run last!)
# ==============================
- name: SSH hardening
hosts: all
become: true
tags: never,hardening
roles:
- role: ssh_hardening
@@ -0,0 +1,23 @@
---
- name: Ensure sudo package is installed
ansible.builtin.package:
name: sudo
state: present
- name: Ensure automation user has passwordless sudo
ansible.builtin.copy:
dest: /etc/sudoers.d/automation
owner: root
group: root
mode: '0440'
content: |
automation ALL=(ALL:ALL) NOPASSWD: ALL
validate: 'visudo -cf %s'
- name: Ensure sudo binary has correct permissions
ansible.builtin.file:
path: /usr/bin/sudo
owner: root
group: root
mode: '4755'
when: ansible_facts.os_family in ["Debian", "RedHat"]
@@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart dockhand
ansible.builtin.systemd:
name: dockhand
state: restarted
@@ -0,0 +1,46 @@
---
- name: Install Docker and Compose
ansible.builtin.package:
name:
- docker.io
- docker-compose-v2
state: present
- name: Ensure Docker is running
ansible.builtin.systemd:
name: docker
enabled: true
state: started
- name: Ensure /docker/dockhand exists
ansible.builtin.file:
path: /docker/dockhand
state: directory
owner: root
group: root
mode: '0755'
- name: Deploy dockhand docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /docker/dockhand/docker-compose.yml
owner: root
group: root
mode: '0644'
notify: Restart dockhand
- name: Deploy dockhand systemd unit
ansible.builtin.template:
src: dockhand.service.j2
dest: /etc/systemd/system/dockhand.service
owner: root
group: root
mode: '0644'
notify: Reload systemd
- name: Enable and start dockhand
ansible.builtin.systemd:
name: dockhand
enabled: true
state: started
daemon_reload: true
@@ -0,0 +1,14 @@
# Managed by Ansible — do not edit by hand.
services:
dockhand:
image: fnsys/dockhand:latest
container_name: dockhand
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- dockhand_data:/app/data
volumes:
dockhand_data:
@@ -0,0 +1,16 @@
[Unit]
Description=dockhand (docker compose stack)
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/docker/dockhand
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,5 @@
---
- name: Restart SSSD
ansible.builtin.service:
name: sssd
state: restarted
@@ -0,0 +1,102 @@
---
- name: Install FreeIPA client packages
ansible.builtin.package:
name:
- freeipa-client
- sssd
- sssd-tools
- oddjob
- oddjob-mkhomedir
state: present
- name: Set hostname FQDN
ansible.builtin.hostname:
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:
- ipa-client-install
- --domain=im.lab
- --realm=IM.LAB
- --server=ipa.im.lab
- "--hostname={{ inventory_hostname }}"
- --mkhomedir
- --principal=admin
- "--password={{ ipa_admin_password }}"
- --unattended
- --force-join
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:
path: /etc/nsswitch.conf
regexp: '^{{ item }}:'
line: '{{ item }}: sss files systemd'
loop:
- passwd
- group
notify: Restart SSSD
- 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
- enable-feature
- with-mkhomedir
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:
name: oddjobd
state: started
enabled: true
- name: Enable and start SSSD
ansible.builtin.service:
name: sssd
state: started
enabled: true
@@ -0,0 +1,8 @@
---
- name: Restart SSH
ansible.builtin.service:
name: "{{ 'sshd'
if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'ssh' }}"
state: restarted
@@ -0,0 +1,29 @@
---
- name: Detect if system is Proxmox
ansible.builtin.stat:
path: /usr/bin/pveversion
register: proxmox_check
- name: Ensure sshd_config.d exists
ansible.builtin.file:
path: /etc/ssh/sshd_config.d
state: directory
- name: Deploy SSH hardening config
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/99-ansible-hardening.conf
mode: '0644'
content: |
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
UsePAM yes
{% if not proxmox_check.stat.exists %}
PermitRootLogin no
{% else %}
PermitRootLogin prohibit-password
{% endif %}
validate: 'sshd -t -f %s'
notify: Restart SSH
@@ -0,0 +1,39 @@
---
# `users` comes from group_vars/all/users.yml
- name: Ensure users exist
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
create_home: true
loop: "{{ users }}"
# --------------------------------------------------
# Configure passwordless sudo safely
# --------------------------------------------------
- name: Configure passwordless sudo
ansible.builtin.copy:
dest: "/etc/sudoers.d/{{ item.name }}"
mode: '0440'
owner: root
group: root
content: |
{{ item.name }} ALL=(ALL:ALL) NOPASSWD: ALL
validate: 'visudo -cf %s'
loop: "{{ users }}"
when: item.sudo_nopasswd | default(false)
# --------------------------------------------------
# Install SSH keys
# --------------------------------------------------
- name: Install authorized SSH keys
ansible.builtin.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_keys | join('\n') }}"
exclusive: true
loop: "{{ users }}"
# --------------------------------------------------
# Reset connection so sudo rules take effect immediately
# --------------------------------------------------
- name: Reset SSH connection
meta: reset_connection
+20 -5
View File
@@ -1,9 +1,24 @@
[linux_servers] [linux_servers_jim]
jimbuntu ansible_host=192.168.19.4 jimbuntu ansible_host=192.168.19.4
jim_storage ansible_host=192.168.19.7 portainer2-jim.im.lab ansible_host=192.168.19.8
portainer2_hellsos ansible_host=192.168.52.9 #galera3 ansible_host=192.168.19.92
portainernode_hellsos ansible_host=192.168.52.21 #galera2 ansible_host=192.168.19.91
portainernode2_jim ansible_host=192.168.19.8 testipaclient ansible_host=192.168.19.98
#testclient ansible_host=192.168.19.115
portainer1-jim.im.lab ansible_host=192.168.19.7
[linux_servers_hellsos]
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] [local]
localhost ansible_connection=local localhost ansible_connection=local
+63 -20
View File
@@ -41,20 +41,20 @@
state: directory state: directory
mode: "0755" mode: "0755"
delegate_to: localhost delegate_to: localhost
tags: backup tags: [backup, never]
- name: Export router config - name: Export router config
community.routeros.command: community.routeros.command:
commands: /export terse show-sensitive commands: /export terse show-sensitive
register: export_cfg register: export_cfg
tags: backup tags: [backup, never]
- name: Save export locally - name: Save export locally
ansible.builtin.copy: ansible.builtin.copy:
content: "{{ export_cfg.stdout[0] }}" content: "{{ export_cfg.stdout[0] }}"
dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc" dest: "{{ backup_dir }}/{{ router_name }}-{{ ts }}.rsc"
delegate_to: localhost delegate_to: localhost
tags: backup tags: [backup, never]
# ---------------------------- # ----------------------------
# Upgrade (tag: upgrade) # Upgrade (tag: upgrade)
@@ -63,46 +63,89 @@
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 tags: [upgrade, never]
- name: Normalize update output
set_fact:
_update_text: "{{ update_check.stdout[0] | replace('\r', '') }}"
tags: [upgrade, never]
- name: Debug raw update output
ansible.builtin.debug:
msg: "{{ _update_text }}"
tags: [upgrade, never]
- name: Parse installed and latest versions - name: Parse installed and latest versions
set_fact: set_fact:
installed_version: "{{ update_check.stdout[0] | regex_search('installed-version: ([\\d.]+)', '\\1') | first }}" installed_version: >-
latest_version: "{{ update_check.stdout[0] | regex_search('latest-version: ([\\d.]+)', '\\1') | first }}" {{
tags: upgrade (_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: 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: Debug parsed versions
ansible.builtin.debug:
msg:
- "Installed: {{ installed_version }}"
- "Latest: {{ latest_version }}"
tags: [upgrade, never]
- name: Skip upgrade if already on latest - name: Skip upgrade if already on latest
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 on latest version {{ installed_version }}. Skipping upgrade."
when: installed_version == latest_version when: installed_version == latest_version
tags: upgrade tags: [upgrade, never]
- name: Trigger package download and install - name: Trigger package download and install
community.routeros.command: community.routeros.command:
commands: /system package update install commands: /system package update install
register: upgrade_result vars:
ansible_command_timeout: 60
when: installed_version != latest_version when: installed_version != latest_version
tags: upgrade ignore_errors: true
tags: [upgrade, never]
- name: Wait before reconnecting after reboot
ansible.builtin.pause:
seconds: 30
when: installed_version != latest_version
tags: [upgrade, never]
- name: Wait for router to come back online after reboot - name: Wait for router to come back online after reboot
ansible.builtin.wait_for_connection: community.routeros.command:
delay: 180 commands: /system resource print
timeout: 300 register: reboot_wait
sleep: 10 until: reboot_wait is succeeded
when: retries: 20
- installed_version != latest_version delay: 15
- upgrade_result is not failed when: installed_version != latest_version
tags: upgrade tags: [upgrade, never]
- name: Confirm upgraded version - name: Confirm upgraded version
community.routeros.command: community.routeros.command:
commands: /system resource print commands: /system resource print
register: post_upgrade_info register: post_upgrade_info
when: installed_version != latest_version when: installed_version != latest_version
tags: upgrade tags: [upgrade, never]
- name: Show post-upgrade RouterOS version - name: Show post-upgrade RouterOS version
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ post_upgrade_info.stdout[0] | regex_search('version: .+') }}" msg: "{{ post_upgrade_info.stdout[0] | regex_search('version: .+') }}"
when: installed_version != latest_version when: installed_version != latest_version
tags: upgrade tags: [upgrade, never]
+6
View File
@@ -0,0 +1,6 @@
- name: Remove SSH key
hosts: all
become: yes
roles:
- role: manage_ssh_keys
remove_user: true
+2 -1
View File
@@ -1,2 +1,3 @@
collections: collections:
- name: community.routeros - name: community.routeros
- name: community.general
+10
View File
@@ -0,0 +1,10 @@
---
# Schedule for our own borgmatic.timer (overrides the package-shipped unit).
# OnCalendar uses systemd.time(7) syntax. RandomizedDelaySec spreads load so
# every host doesn't hit the borg server at the same instant.
borgmatic_oncalendar: "*-*-* 03:00:00"
borgmatic_randomized_delay_sec: 3h
borgmatic_persistent: true
# Extra flags passed to the borgmatic invocation in our borgmatic.service.
borgmatic_verbosity_args: "--verbosity -1 --syslog-verbosity 1"
+131
View File
@@ -0,0 +1,131 @@
---
- name: Login to borg controller
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/auth/login"
method: POST
body_format: json
body:
username: "{{ borgcontroller_username }}"
password: "{{ borgcontroller_password }}"
status_code: 200
delegate_to: localhost
become: false
register: _bc_login
no_log: true
- name: Get borg server SSH endpoint
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/config"
method: GET
headers:
Cookie: "{{ _bc_login.cookies_string }}"
delegate_to: localhost
become: false
register: _bc_config
- name: List repositories
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/repositories"
method: GET
headers:
Cookie: "{{ _bc_login.cookies_string }}"
delegate_to: localhost
become: false
register: _bc_repos
- name: Find existing repository for this host
ansible.builtin.set_fact:
_bc_existing: >-
{{ _bc_repos.json | selectattr('alias', 'eq', inventory_hostname) | list }}
- name: Create repository if missing
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/repositories"
method: POST
body_format: json
body:
alias: "{{ inventory_hostname }}"
sshPublicKey: "{{ root_ssh.ssh_public_key }}"
storageSize: "{{ backup_hosts[inventory_hostname].storage_size_gb | int }}"
headers:
Cookie: "{{ _bc_login.cookies_string }}"
status_code: [200, 201]
delegate_to: localhost
become: false
when: _bc_existing | length == 0
- name: Update repository SSH key if root's key changed
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/repositories/{{ _bc_existing[0].id }}"
method: PATCH
body_format: json
body:
sshPublicKey: "{{ root_ssh.ssh_public_key }}"
headers:
Cookie: "{{ _bc_login.cookies_string }}"
status_code: 200
delegate_to: localhost
become: false
when:
- _bc_existing | length > 0
- _bc_existing[0].sshPublicKey != root_ssh.ssh_public_key
- name: Re-list repositories after possible create/update
ansible.builtin.uri:
url: "{{ borgcontroller_url }}/api/repositories"
method: GET
headers:
Cookie: "{{ _bc_login.cookies_string }}"
delegate_to: localhost
become: false
register: _bc_repos_after
- name: Resolve repository for this host
ansible.builtin.set_fact:
_bc_repo: >-
{{ (_bc_repos_after.json | selectattr('alias', 'eq', inventory_hostname) | list)[0] }}
- name: Build borg SSH URI
ansible.builtin.set_fact:
borgcontroller_repo_uri: "ssh://{{ _bc_config.json.borgSshHost }}/./{{ _bc_repo.id }}"
_bc_borg_host: "{{ _bc_config.json.borgSshHost.split('@')[1].split(':')[0] }}"
_bc_borg_port: "{{ _bc_config.json.borgSshHost.split('@')[1].split(':')[1] | default('22') }}"
- name: Ensure /root/.ssh exists
ansible.builtin.file:
path: /root/.ssh
state: directory
owner: root
group: root
mode: '0700'
- name: Scan borg server SSH host key
ansible.builtin.command: ssh-keyscan -p {{ _bc_borg_port }} {{ _bc_borg_host }}
register: _bc_keyscan
changed_when: false
check_mode: false
- name: Trust borg server SSH host key (root known_hosts)
ansible.builtin.lineinfile:
path: /root/.ssh/known_hosts
line: "{{ item }}"
create: true
owner: root
group: root
mode: '0600'
loop: "{{ _bc_keyscan.stdout_lines }}"
when:
- item | length > 0
- not item.startswith('#')
- name: Initialize borg repository (no-op if already initialized)
ansible.builtin.command:
cmd: borg init --encryption=repokey-blake2 {{ borgcontroller_repo_uri }}
environment:
BORG_PASSPHRASE: "{{ borg_passphrase }}"
register: _borg_init
changed_when: _borg_init.rc == 0
failed_when:
- _borg_init.rc != 0
- "'already exists' not in (_borg_init.stderr | default(''))"
no_log: true
+105
View File
@@ -0,0 +1,105 @@
---
- name: Skip hosts without backup config
ansible.builtin.debug:
msg: "No entry in backup_hosts for {{ inventory_hostname }}; skipping backup role."
when: inventory_hostname not in (backup_hosts | default({}))
- name: Configure borgmatic
when: inventory_hostname in (backup_hosts | default({}))
block:
- name: Ensure borg_passphrase is set (Semaphore secret)
ansible.builtin.assert:
that:
- borg_passphrase is defined
- borg_passphrase | length > 0
fail_msg: "borg_passphrase must be defined (provided by Semaphore secrets)"
- name: Install borgmatic
ansible.builtin.package:
name: borgmatic
state: present
- name: Ensure /etc/borgmatic exists
ansible.builtin.file:
path: /etc/borgmatic
state: directory
owner: root
group: root
mode: '0750'
- name: Write borg passphrase file
ansible.builtin.copy:
dest: /etc/borgmatic/passphrase
content: "{{ borg_passphrase }}"
owner: root
group: root
mode: '0600'
no_log: true
- name: Ensure root has an SSH key for the borg server
ansible.builtin.user:
name: root
generate_ssh_key: true
ssh_key_type: ed25519
ssh_key_file: .ssh/id_ed25519
ssh_key_comment: "borgmatic@{{ inventory_hostname }}"
register: root_ssh
- name: Register / look up repository on borg controller
ansible.builtin.include_tasks: borgcontroller.yml
when:
- borgcontroller_username is defined
- borgcontroller_password is defined
- name: Build borgmatic config (strip controller-only keys, inject repository + passcommand)
ansible.builtin.set_fact:
_borgmatic_config: >-
{{
(backup_hosts[inventory_hostname]
| dict2items
| rejectattr('key', 'in', ['storage_size_gb'])
| items2dict)
| combine({'encryption_passcommand': 'cat /etc/borgmatic/passphrase'})
| combine(
{'repositories': [{'path': borgcontroller_repo_uri, 'label': inventory_hostname}]}
if borgcontroller_repo_uri is defined else {}
)
}}
- name: Deploy borgmatic config
ansible.builtin.template:
src: borgmatic.yaml.j2
dest: /etc/borgmatic/config.yaml
owner: root
group: root
mode: '0640'
- name: Deploy borgmatic systemd service (overrides package unit)
ansible.builtin.template:
src: borgmatic.service.j2
dest: /etc/systemd/system/borgmatic.service
owner: root
group: root
mode: '0644'
register: _borgmatic_service_unit
- name: Deploy borgmatic systemd timer (overrides package unit)
ansible.builtin.template:
src: borgmatic.timer.j2
dest: /etc/systemd/system/borgmatic.timer
owner: root
group: root
mode: '0644'
register: _borgmatic_timer_unit
- name: Reload systemd if units changed
ansible.builtin.systemd:
daemon_reload: true
when: _borgmatic_service_unit is changed or _borgmatic_timer_unit is changed
- name: Enable and start borgmatic timer
ansible.builtin.systemd:
name: borgmatic.timer
enabled: true
state: started
@@ -0,0 +1,21 @@
# Managed by Ansible — do not edit by hand.
[Unit]
Description=borgmatic backup
Wants=network-online.target
After=network-online.target
# Don't run on battery power.
ConditionACPower=true
[Service]
Type=oneshot
# Lower priority so backups don't starve foreground work.
Nice=19
CPUSchedulingPolicy=batch
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOWeight=100
Restart=no
# Prevent rate limiting of borgmatic log events.
LogRateLimitIntervalSec=0
# Delay start by a random amount handled in the timer; keep the service simple.
ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /usr/bin/borgmatic {{ borgmatic_verbosity_args }}
+11
View File
@@ -0,0 +1,11 @@
# Managed by Ansible — do not edit by hand.
[Unit]
Description=Run borgmatic backup
[Timer]
OnCalendar={{ borgmatic_oncalendar }}
RandomizedDelaySec={{ borgmatic_randomized_delay_sec }}
Persistent={{ borgmatic_persistent | bool | lower }}
[Install]
WantedBy=timers.target
+3
View File
@@ -0,0 +1,3 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# Managed by Ansible — do not edit by hand.
{{ _borgmatic_config | to_nice_yaml(indent=2, width=1000) }}
@@ -0,0 +1,4 @@
- name: Add user and authorized key
authorized_keys:
user: "{{ user }}"
key: "{{ key }}"
+5
View File
@@ -0,0 +1,5 @@
- include_tasks: add_ssh_key.yml
when: add_user | default(false)
- include_tasks: remove_ssh_key.yml
when: remove_user | default(false)
@@ -0,0 +1,10 @@
- name: Remove authorized key
authorized_keys:
user: "{{ user }}"
key: "{{ key }}"
state: absent
- name: Ensure user is absent
user:
name: "{{ user }}"
state: absent
+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
+35 -24
View File
@@ -2,29 +2,7 @@
- name: Baseline user setup - name: Baseline user setup
hosts: all hosts: all
become: true become: true
tags: users
vars:
users:
- name: automation
shell: /bin/bash
groups: []
sudo_nopasswd: true
ssh_keys:
- "ssh-ed25519 AAAAC3..."
- name: hellsos
shell: /bin/bash
groups: []
sudo_nopasswd: true
ssh_keys:
- "ssh-ed25519 AAAAC3..."
- name: jim
shell: /bin/bash
groups: []
sudo_nopasswd: true
ssh_keys:
- "ssh-ed25519 AAAAC3..."
tasks: tasks:
@@ -108,4 +86,37 @@
if ansible_facts.os_family in if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse'] ['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'ssh' }}" else 'ssh' }}"
state: restarted state: restarted
# ==============================
# THIRD PLAY: HOSTNAME
# ==============================
- name: Set hostname from inventory
hosts: all
become: true
tags: never,hostname
tasks:
- name: Set system hostname to inventory_hostname
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"
# ==============================
# FOURTH PLAY: FREEIPA / SSSD
# ==============================
- name: FreeIPA client setup
hosts: all
become: true
tags: never,sssd
roles:
- role: initial_install/roles/freeipa_client
# ==============================
# FIFTH PLAY: BACKUP
# ==============================
- import_playbook: backup.yml
+1 -1
View File
@@ -7,7 +7,7 @@
sms_username: "mikrotik" sms_username: "mikrotik"
sms_password_send: "jdkotzHJIOPWhjtr32D" sms_password_send: "jdkotzHJIOPWhjtr32D"
sms_password_recv: "jdkotzHJIOPWhjtr32D" sms_password_recv: "jdkotzHJIOPWhjtr32D"
sms_wait_seconds: 120 # Wait 2 minutes for delivery sms_wait_seconds: 15 # Wait 15s for delivery
tasks: tasks:
- name: Generate random test string - name: Generate random test string
+95 -45
View File
@@ -1,91 +1,141 @@
--- ---
- name: Update system (APT + Flatpak) - name: Update Linux system
hosts: all hosts: all:!localhost:!portainer2-jim.im.lab
become: yes become: true
gather_facts: yes gather_facts: false
serial: 5 serial: 5
tasks: tasks:
# ---------------------------------------------------------
# Connectivity
# ---------------------------------------------------------
- name: Ensure SSH is reachable (skip host if not) - name: Ensure SSH is reachable (skip host if not)
delegate_to: localhost ansible.builtin.wait_for:
wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}" host: "{{ ansible_host | default(inventory_hostname) }}"
port: 22 port: 22
timeout: 5 timeout: 5
delegate_to: localhost
register: ssh_check 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 when: ssh_check is failed
- name: Gather facts
ansible.builtin.setup:
- name: Ping with retries (handle intermittent flaps) - name: Ping with retries (handle intermittent flaps)
ping: ansible.builtin.ping:
register: ping_result register: ping_result
retries: 5 retries: 5
delay: 5 delay: 5
until: ping_result is success until: ping_result is success
- name: Wait for apt lock to be released # ---------------------------------------------------------
shell: | # Distribution packages
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do # ---------------------------------------------------------
echo "Waiting for apt lock..."
sleep 5
done
changed_when: false
- name: Update apt cache - name: Update Debian-family packages
apt: ansible.builtin.include_role:
update_cache: yes name: update_apt
when: ansible_facts.os_family == 'Debian'
- name: Perform full upgrade - name: Update Arch Linux packages
apt: ansible.builtin.include_role:
upgrade: full name: update_arch
autoremove: yes when: ansible_facts.os_family == 'Archlinux'
autoclean: yes
register: apt_upgrade
retries: 3
delay: 10
until: apt_upgrade is succeeded
- name: Fix broken packages - name: Skip unsupported Linux distribution
command: apt-get -f install -y ansible.builtin.debug:
register: fix_result msg: >-
failed_when: false Package updates are not configured for
changed_when: "'Setting up' in fix_result.stdout" {{ ansible_facts.distribution }} ({{ ansible_facts.os_family }}).
when: ansible_facts.os_family not in ['Debian', 'Archlinux']
# ---------------------------------------------------------
# Flatpak
# ---------------------------------------------------------
- name: Check if Flatpak is installed - name: Check if Flatpak is installed
command: which flatpak ansible.builtin.command:
cmd: which flatpak
register: flatpak_check register: flatpak_check
changed_when: false changed_when: false
failed_when: false failed_when: false
- name: Update system Flatpaks - name: Update system Flatpaks
command: flatpak update -y --noninteractive --system ansible.builtin.command:
when: flatpak_check.rc == 0 cmd: flatpak update -y --noninteractive --system
register: flatpak_system_update
changed_when: "'Nothing to do' not in flatpak_system_update.stdout"
failed_when: false failed_when: false
when: flatpak_check.rc == 0
- name: Update user Flatpaks - name: Update user Flatpaks
command: flatpak update -y --noninteractive --user ansible.builtin.command:
cmd: flatpak update -y --noninteractive --user
become: false 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 failed_when: false
when: flatpak_check.rc == 0
- name: Remove unused Flatpaks - 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 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 failed_when: false
- name: Update snap packages - 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 failed_when: false
when: snap_check.rc == 0
# ---------------------------------------------------------
# Reboot check
# ---------------------------------------------------------
- name: Check if reboot is required - name: Check if reboot is required
stat: ansible.builtin.stat:
path: /var/run/reboot-required path: /var/run/reboot-required
register: reboot_required register: reboot_required
- name: Notify if reboot required - name: Notify if reboot required
debug: ansible.builtin.debug:
msg: "Reboot required on {{ inventory_hostname }}" 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