5 Commits
Author SHA1 Message Date
fencl 595c0624d6 Merge pull request 'edit init 1' (#1) from edit into main
Reviewed-on: IM/ansible_fencl#1
2025-10-03 11:47:51 +00:00
fencl 2b5a2b4a1a edit init 1 2025-10-03 13:36:35 +02:00
jakub b247ea0832 Update mikrotikbackup.yml 2025-09-19 10:58:22 +00:00
jakub c476f04a8e Update inv_mikrotiks 2025-09-19 09:30:23 +00:00
jakub 5c185324d5 Update inv_linuxes 2025-09-18 12:31:07 +00:00
45 changed files with 336 additions and 1392 deletions
-10
View File
@@ -1,10 +0,0 @@
# Python virtual environments
.venv/
venv/
# MikroTik backup output
mikrotik/backups/
mikrotik/output/
# Claude Code local state
.claude/
-8
View File
@@ -1,8 +0,0 @@
---
- name: Install borgmatic and deploy per-host config
hosts: all
become: true
tags: never,backup
roles:
- role: backup
+29
View File
@@ -0,0 +1,29 @@
---
- name: Upload and run stack health checks
hosts: proxmox
become: true
vars:
health_script_path: /data/compose/nextcloud/stack-health.sh
tasks:
- name: Upload stack-health.sh
ansible.builtin.copy:
src: files/stack-health.sh
dest: "{{ health_script_path }}"
mode: '0755'
- name: Run stack-health.sh
ansible.builtin.shell: "{{ health_script_path }}"
register: health
args:
executable: /bin/bash
- name: Show health output
ansible.builtin.debug:
msg: "{{ health.stdout | default('no stdout') }}"
- name: Fail if checks failed (rc != 0)
ansible.builtin.fail:
msg: "Health checks failed"
when: health.rc != 0
+30
View File
@@ -0,0 +1,30 @@
---
- name: Update Collabora (pull + recreate in same compose project)
hosts: proxmox
become: true
vars:
collabora_compose_path: /data/compose/nextcloud/collabora-only.yml
collabora_project_name: nextcloud-collabora # based on your labels
tasks:
- name: Pull collabora/code:latest image
community.docker.docker_image:
name: collabora/code
tag: latest
source: pull
# Compose file contains only service "collabora", so this acts on that service only
- name: Compose pull (ensure freshest image)
community.docker.docker_compose_v2:
project_name: "{{ collabora_project_name }}"
files: ["{{ collabora_compose_path }}"]
pull: always
state: present
- name: Recreate collabora with new image
community.docker.docker_compose_v2:
project_name: "{{ collabora_project_name }}"
files: ["{{ collabora_compose_path }}"]
recreate: always
state: present
-25
View File
@@ -1,25 +0,0 @@
---
# 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
@@ -1,22 +0,0 @@
---
# 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"
-39
View File
@@ -1,39 +0,0 @@
- name: Update Homarr
hosts: linux_servers
become: true
gather_facts: false
vars:
homarr_project: homarr
homarr_compose_file: /data/compose/homarr/docker-compose-homarr.yml
homarr_service: homarr
homarr_port: 7575
tasks:
- name: Pull latest Homarr image
community.docker.docker_compose_v2:
project_src: "{{ homarr_compose_file | dirname }}"
files:
- "{{ homarr_compose_file | basename }}"
pull: always
- name: Recreate Homarr service
community.docker.docker_compose_v2:
project_src: "{{ homarr_compose_file | dirname }}"
files:
- "{{ homarr_compose_file | basename }}"
services:
- "{{ homarr_service }}"
state: present
recreate: always
- name: Wait for Homarr port
ansible.builtin.wait_for:
host: 127.0.0.1
port: "{{ homarr_port }}"
timeout: 60
- name: Check Homarr HTTP endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ homarr_port }}/"
status_code: 200
@@ -1,2 +0,0 @@
collections:
- name: freeipa.ansible_freeipa
-47
View File
@@ -1,47 +0,0 @@
---
- 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
@@ -1,23 +0,0 @@
---
- 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"]
@@ -1,9 +0,0 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart dockhand
ansible.builtin.systemd:
name: dockhand
state: restarted
@@ -1,46 +0,0 @@
---
- 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
@@ -1,14 +0,0 @@
# 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:
@@ -1,16 +0,0 @@
[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
@@ -1,5 +0,0 @@
---
- name: Restart SSSD
ansible.builtin.service:
name: sssd
state: restarted
@@ -1,102 +0,0 @@
---
- 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
@@ -1,8 +0,0 @@
---
- name: Restart SSH
ansible.builtin.service:
name: "{{ 'sshd'
if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'ssh' }}"
state: restarted
@@ -1,29 +0,0 @@
---
- 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
@@ -1,39 +0,0 @@
---
# `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
+2 -24
View File
@@ -1,24 +1,2 @@
[linux_servers_jim] [linux_servers]
jimbuntu ansible_host=192.168.19.4 proxmox ansible_host=192.168.69.2
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
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]
localhost ansible_connection=local
+2 -16
View File
@@ -1,16 +1,2 @@
[mikrotik_routers] [mikrotiks]
jim_main ansible_host=192.168.19.2 main_mikrotik_fencl ansible_host=192.168.69.1
jim_gw2 ansible_host=192.168.19.3
hellsos ansible_host=192.168.40.1
ewolet ansible_host=192.168.90.1
Poli ansible_host=192.168.2.1
Schmid ansible_host=192.168.177.1
#Volf ansible_host=192.168.88.1
fencl_home ansible_host=192.168.68.1
fencl_tata ansible_host=192.168.69.1
[mikrotik_routers:vars]
ansible_connection=network_cli
ansible_network_os=community.routeros.routeros
ansible_command_timeout=15
+1 -1
View File
@@ -33,7 +33,7 @@
current_date: "{{ date_output.stdout }}" current_date: "{{ date_output.stdout }}"
- name: Export router config - name: Export router config
shell: timeout 15 ssh -o StrictHostKeyChecking=no {{ ansible_user }}@{{ ansible_host }} -p {{ ansible_port }} "/export show-sensitive" shell: timeout 15 ssh -o StrictHostKeyChecking=no {{ ansible_user }}@{{ ansible_host }} -p {{ ansible_port }} "/export"
register: export_output register: export_output
delegate_to: localhost delegate_to: localhost
when: system_identity.rc == 0 when: system_identity.rc == 0
-151
View File
@@ -1,151 +0,0 @@
- name: Backup and/or Upgrade MikroTik
hosts: mikrotik_routers
gather_facts: no
vars:
backup_dir: /opt/mikrotik_backups/
tasks:
# ----------------------------
# Always: identity + timestamp
# ----------------------------
- name: Get router identity
community.routeros.command:
commands: /system identity print
register: identity_raw
tags: always
- name: Parse router name
set_fact:
router_name: "{{ identity_raw.stdout[0].split(': ')[1] | trim }}"
tags: always
- name: Get timestamp
ansible.builtin.command: date +%Y-%m-%d_%H-%M-%S
register: date_out
delegate_to: localhost
tags: always
- name: Set timestamp fact
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
tags: [backup, never]
- name: Export router config
community.routeros.command:
commands: /export terse show-sensitive
register: export_cfg
tags: [backup, never]
- 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]
# ----------------------------
# Upgrade (tag: upgrade)
# ----------------------------
- name: Check current and latest available package versions
community.routeros.command:
commands: /system package update check-for-updates
register: update_check
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
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: 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
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: Trigger package download and install
community.routeros.command:
commands: /system package update install
vars:
ansible_command_timeout: 60
when: installed_version != latest_version
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
community.routeros.command:
commands: /system resource print
register: reboot_wait
until: reboot_wait is succeeded
retries: 20
delay: 15
when: installed_version != latest_version
tags: [upgrade, never]
- name: Confirm upgraded version
community.routeros.command:
commands: /system resource print
register: post_upgrade_info
when: installed_version != latest_version
tags: [upgrade, never]
- 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]
+38
View File
@@ -0,0 +1,38 @@
---
- name: Nextcloud backup (config, custom_apps, DB)
hosts: proxmox
become: true
vars:
nc_root: /data/compose/nextcloud
backup_dir: "{{ nc_root }}/backup-{{ ansible_date_time.date }}"
db_container: nextcloud-db
tasks:
- name: Ensure backup directory exists
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
# Use archive module to create tar.gz directly on the remote host
- name: Archive config directory
ansible.builtin.archive:
path: "{{ nc_root }}/config"
dest: "{{ backup_dir }}/config.tgz"
format: gz
- name: Archive custom_apps directory
ansible.builtin.archive:
path: "{{ nc_root }}/custom_apps"
dest: "{{ backup_dir }}/custom_apps.tgz"
format: gz
# Dump DB directly to a file on the host (avoid shuttling dump through Ansible)
- name: Dump MariaDB from container to file
ansible.builtin.shell: |
set -euo pipefail
docker exec {{ db_container }} sh -c 'command -v mariadb-dump >/dev/null && mariadb-dump -u"$$MYSQL_USER" -p"$$MYSQL_PASSWORD" "$$MYSQL_DATABASE" || mysqldump -u"$$MYSQL_USER" -p"$$MYSQL_PASSWORD" "$$MYSQL_DATABASE"' \
> "{{ backup_dir }}/db.sql"
args:
executable: /bin/bash
+112
View File
@@ -0,0 +1,112 @@
---
- name: Upgrade Nextcloud to 31-apache (pull + recreate + occ)
hosts: proxmox
become: true
vars:
nc_container: nextcloud
nc_image_tag: "31-apache" # change to 32-apache when you step to next major
# Ports/volumes/env exactly as you use:
nc_root: /data/compose/nextcloud
nc_http_port: "8080:80"
tasks:
- name: Gather nextcloud container info
community.docker.docker_container_info:
name: "{{ nc_container }}"
register: nc_info
- name: Derive compose project & network from existing container
ansible.builtin.set_fact:
nc_project: "{{ nc_info.container.Config.Labels['com.docker.compose.project'] | default('nextcloud') }}"
nc_networks: "{{ (nc_info.container.NetworkSettings.Networks | default({})).keys() | list }}"
nc_net_primary: "{{ (nc_info.container.NetworkSettings.Networks | default({})).keys() | list | first }}"
when: nc_info.exists
- name: Enable maintenance mode
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ maintenance:mode --on
- name: Render one-off compose for nextcloud (single-service)
ansible.builtin.copy:
dest: /tmp/nc.yml
mode: '0644'
content: |
name: {{ nc_project }}
services:
nextcloud:
image: nextcloud:{{ nc_image_tag }}
container_name: {{ nc_container }}
restart: unless-stopped
networks: [cloud]
ports: ["{{ nc_http_port }}"]
volumes:
- {{ nc_root }}/config:/var/www/html/config
- {{ nc_root }}/data:/var/www/html/data
- {{ nc_root }}/custom_apps:/var/www/html/custom_apps
environment:
TZ: Europe/Prague
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: dbpassword
MYSQL_HOST: nextclouddb
REDIS_HOST: redis
NEXTCLOUD_ADMIN_USER: root
NEXTCLOUD_ADMIN_PASSWORD: '1234SilneHeslo.-.'
networks:
cloud:
external: true
name: {{ nc_net_primary }}
- name: Pull the new Nextcloud image
community.docker.docker_compose_v2:
project_name: "{{ nc_project }}"
files: ["/tmp/nc.yml"]
pull: always
state: present
- name: Recreate Nextcloud with the new image
community.docker.docker_compose_v2:
project_name: "{{ nc_project }}"
files: ["/tmp/nc.yml"]
recreate: always
state: present
- name: Run occ upgrade
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ upgrade
- name: Recommended DB maintenance (safe to run)
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ db:add-missing-indices
ignore_errors: true
- name: Convert filecache bigint (safe)
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ db:convert-filecache-bigint --no-interaction
ignore_errors: true
- name: Disable maintenance mode
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ maintenance:mode --off
- name: Show status
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ status
register: nc_status
- name: Print status
ansible.builtin.debug:
msg: "{{ nc_status.stdout | default('no output') }}"
+75
View File
@@ -0,0 +1,75 @@
---
- name: Update Redis (pull + recreate, same stack)
hosts: proxmox
become: true
vars:
nc_container: nextcloud
redis_container: redis
redis_image: "redis:7-alpine"
nc_root: /data/compose/nextcloud
tasks:
- name: Gather nextcloud container info (to learn project + network)
community.docker.docker_container_info:
name: "{{ nc_container }}"
register: nc_info
- name: Derive compose project & network
ansible.builtin.set_fact:
nc_project: "{{ nc_info.container.Config.Labels['com.docker.compose.project'] | default('nextcloud') }}"
nc_net_primary: "{{ (nc_info.container.NetworkSettings.Networks | default({})).keys() | list | first }}"
when: nc_info.exists
- name: Enable maintenance mode (optional safety)
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ maintenance:mode --on
ignore_errors: true
- name: Render one-off compose for Redis
ansible.builtin.copy:
dest: /tmp/redis.yml
mode: '0644'
content: |
name: {{ nc_project }}
services:
redis:
image: {{ redis_image }}
container_name: {{ redis_container }}
restart: unless-stopped
networks: [cloud]
volumes:
- {{ nc_root }}/redis:/data
networks:
cloud:
external: true
name: {{ nc_net_primary }}
- name: Pull redis image
community.docker.docker_compose_v2:
project_name: "{{ nc_project }}"
files: ["/tmp/redis.yml"]
pull: always
state: present
- name: Recreate redis
community.docker.docker_compose_v2:
project_name: "{{ nc_project }}"
files: ["/tmp/redis.yml"]
recreate: always
state: present
- name: Disable maintenance mode (if we turned it on)
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php occ maintenance:mode --off
ignore_errors: true
- name: Fire one cron tick (cleanup pending jobs)
community.docker.docker_container_exec:
container: "{{ nc_container }}"
user: "www-data"
command: php -f /var/www/html/cron.php
-6
View File
@@ -1,6 +0,0 @@
- name: Remove SSH key
hosts: all
become: yes
roles:
- role: manage_ssh_keys
remove_user: true
+3 -2
View File
@@ -1,3 +1,4 @@
---
collections: collections:
- name: community.routeros - name: community.docker
- name: community.general - name: ansible.posix
-10
View File
@@ -1,10 +0,0 @@
---
# 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
@@ -1,131 +0,0 @@
---
- 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
@@ -1,105 +0,0 @@
---
- 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
@@ -1,21 +0,0 @@
# 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
@@ -1,11 +0,0 @@
# 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
@@ -1,3 +0,0 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# Managed by Ansible — do not edit by hand.
{{ _borgmatic_config | to_nice_yaml(indent=2, width=1000) }}
@@ -1,4 +0,0 @@
- name: Add user and authorized key
authorized_keys:
user: "{{ user }}"
key: "{{ key }}"
-5
View File
@@ -1,5 +0,0 @@
- include_tasks: add_ssh_key.yml
when: add_user | default(false)
- include_tasks: remove_ssh_key.yml
when: remove_user | default(false)
@@ -1,10 +0,0 @@
- 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
@@ -1,23 +0,0 @@
---
- 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
@@ -1,9 +0,0 @@
---
- 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
@@ -1,5 +0,0 @@
---
# 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
@@ -1,72 +0,0 @@
---
- 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
-122
View File
@@ -1,122 +0,0 @@
---
- name: Baseline user setup
hosts: all
become: true
tags: users
tasks:
- name: Pick sudo group per distro
set_fact:
sudo_group: >-
{{ 'wheel'
if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'sudo' }}
- name: Ensure user exists
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
groups: "{{ sudo_group }}"
append: true
create_home: true
loop: "{{ users }}"
- name: Enforce authorized SSH keys
ansible.builtin.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_keys | join('\n') }}"
exclusive: true
loop: "{{ users }}"
- name: Grant passwordless sudo
ansible.builtin.copy:
dest: "/etc/sudoers.d/{{ item.name }}"
mode: '0440'
content: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL\n"
validate: 'visudo -cf %s'
loop: "{{ users }}"
when: item.sudo_nopasswd
# ==============================
# SECOND PLAY: SSH HARDENING
# ==============================
- name: SSH Hardening
hosts: all
become: true
tags: never,hardening
tasks:
- name: Detect if system is Proxmox
ansible.builtin.stat:
path: /usr/bin/pveversion
register: proxmox_check
- name: Ensure sshd_config.d directory 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
handlers:
- name: Restart SSH
ansible.builtin.service:
name: "{{ 'sshd'
if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'ssh' }}"
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
-57
View File
@@ -1,57 +0,0 @@
---
- name: Send and verify SMS delivery via internet-master.cz
hosts: localhost
gather_facts: false
vars:
sms_number: "601358865"
sms_username: "mikrotik"
sms_password_send: "jdkotzHJIOPWhjtr32D"
sms_password_recv: "jdkotzHJIOPWhjtr32D"
sms_wait_seconds: 15 # Wait 15s for delivery
tasks:
- name: Generate random test string
set_fact:
random_string: "mikrotik_{{ lookup('password', '/dev/null length=8 chars=ascii_letters') }}"
- name: Send SMS message
uri:
url: "https://sms.internet-master.cz/send/?number={{ sms_number }}&message=@mikrotik@{{ random_string | urlencode }}&type=class-1&username={{ sms_username }}&password={{ sms_password_send }}"
method: GET
return_content: true
register: send_result
- name: Show send API response
debug:
var: send_result.content
- name: Wait for SMS to be delivered
pause:
seconds: "{{ sms_wait_seconds }}"
- name: Fetch received messages
uri:
url: "https://sms.internet-master.cz/receive/?username={{ sms_username }}&password={{ sms_password_recv }}"
method: GET
return_content: true
register: recv_result
- name: Parse received JSON
set_fact:
inbox: "{{ recv_result.json.inbox | default([]) }}"
- name: Check if random string message was received
set_fact:
message_found: "{{ inbox | selectattr('message', 'equalto', random_string) | list | length > 0 }}"
- name: Report result
debug:
msg: >
SMS with message '{{ random_string }}' was {{
'delivered ✅' if message_found else 'NOT delivered ❌'
}}.
- name: Fail if not delivered
fail:
msg: "Message '{{ random_string }}' not found in received inbox!"
when: not message_found
+26 -133
View File
@@ -1,141 +1,34 @@
--- - name: Update system (APT + Flatpak)
- name: Update Linux system hosts: all
hosts: all:!localhost:!portainer2-jim.im.lab
become: true become: true
gather_facts: false become_user: root
serial: 5 become_method: sudo
tasks: tasks:
- name: Update APT cache
apt:
update_cache: yes
# --------------------------------------------------------- - name: Upgrade all APT packages
# Connectivity apt:
# --------------------------------------------------------- upgrade: dist
- name: Ensure SSH is reachable (skip host if not) - name: Check if flatpak binary exists
ansible.builtin.wait_for: stat:
host: "{{ ansible_host | default(inventory_hostname) }}" path: /usr/bin/flatpak
port: 22 register: flatpak_bin
timeout: 5
delegate_to: localhost
register: ssh_check
ignore_errors: true
become: false
- 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)
ansible.builtin.ping:
register: ping_result
retries: 5
delay: 5
until: ping_result is success
# ---------------------------------------------------------
# Distribution packages
# ---------------------------------------------------------
- name: Update Debian-family packages
ansible.builtin.include_role:
name: update_apt
when: ansible_facts.os_family == 'Debian'
- name: Update Arch Linux packages
ansible.builtin.include_role:
name: update_arch
when: ansible_facts.os_family == 'Archlinux'
- 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
ansible.builtin.command:
cmd: which flatpak
register: flatpak_check
changed_when: false
failed_when: false
- name: Update system Flatpaks - name: Update system Flatpaks
ansible.builtin.command: shell: timeout 300 flatpak update -y
cmd: flatpak update -y --noninteractive --system register: flatpak_sys
register: flatpak_system_update failed_when: flatpak_sys.rc != 0 and flatpak_sys.rc != 124
changed_when: "'Nothing to do' not in flatpak_system_update.stdout" when: flatpak_bin.stat.exists
failed_when: false
when: flatpak_check.rc == 0
- name: Update user Flatpaks - name: Update user Flatpaks
ansible.builtin.command: become_user: jakub
cmd: flatpak update -y --noninteractive --user environment:
become: false XDG_RUNTIME_DIR: /run/user/1000
register: flatpak_user_update shell: timeout 300 flatpak update -y
changed_when: "'Nothing to do' not in flatpak_user_update.stdout" register: flatpak_user
failed_when: false failed_when: flatpak_user.rc != 0 and flatpak_user.rc != 124
when: flatpak_check.rc == 0 when: flatpak_bin.stat.exists
- name: Remove unused Flatpaks
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
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
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Notify if reboot required
ansible.builtin.debug:
msg: "Reboot required on {{ inventory_hostname }}"
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
+13 -22
View File
@@ -1,12 +1,15 @@
--- # users-ssh-nopasswd.yml
- name: Ensure users, SSH keys, and passwordless sudo - name: Ensure users, SSH keys, and passwordless sudo
hosts: all hosts: all
become: true become: true
become_user: root
become_method: sudo
vars: vars:
users: users:
- name: automation - name: automation
shell: /bin/bash shell: /bin/bash
# optional extra groups besides sudo/wheel
groups: [] groups: []
sudo_nopasswd: true sudo_nopasswd: true
keys: keys:
@@ -27,43 +30,33 @@
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPFS4fsqMjMMu/Bi/884bw7yJBqvWusDRESvanH6Owco jakub@jimbuntu" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPFS4fsqMjMMu/Bi/884bw7yJBqvWusDRESvanH6Owco jakub@jimbuntu"
tasks: tasks:
- name: Pick sudo group per distro - name: Pick sudo group per distro
ansible.builtin.set_fact: ansible.builtin.set_fact:
sudo_group: >- sudo_group: "{{ 'wheel' if ansible_facts.os_family in ['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse'] else 'sudo' }}"
{{ 'wheel'
if ansible_facts.os_family in
['RedHat','Rocky','AlmaLinux','Fedora','OracleLinux','Suse']
else 'sudo' }}
- name: Ensure user exists (creates home) - name: Ensure user exists (creates home)
ansible.builtin.user: ansible.builtin.user:
name: "{{ item.name }}" name: "{{ item.name }}"
shell: "{{ item.shell | default(omit) }}" shell: "{{ item.shell | default('/bin/bash') }}"
groups: >- groups: >-
{{ ( {{ (
(item.groups | default([])) (item.groups | default([]))
+ ([sudo_group] if item.sudo_nopasswd | default(false) else []) + ([sudo_group] if item.sudo_nopasswd | default(false) else [])
) | unique | join(',') ) | unique | join(',') if
if ( ((item.groups | default([])) | length > 0) or (item.sudo_nopasswd | default(false))
(item.groups | default([]) | length > 0)
or item.sudo_nopasswd | default(false)
)
else omit }} else omit }}
append: true append: true
create_home: true create_home: true
state: present state: present
loop: "{{ users }}" loop: "{{ users }}"
- name: Enforce authorized SSH keys - name: Install authorized SSH keys
ansible.builtin.authorized_key: ansible.builtin.authorized_key:
user: "{{ item.name }}" user: "{{ item.0.name }}"
key: "{{ item.keys | join('\n') }}" key: "{{ item.1 }}"
state: present state: present
manage_dir: true manage_dir: true
exclusive: true loop: "{{ users | subelements('keys', skip_missing=True) }}"
loop: "{{ users }}"
when: item.keys is defined
- name: Grant passwordless sudo via sudoers.d - name: Grant passwordless sudo via sudoers.d
ansible.builtin.copy: ansible.builtin.copy:
@@ -71,9 +64,7 @@
owner: root owner: root
group: root group: root
mode: '0440' mode: '0440'
content: | content: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL"
# Managed by Ansible
{{ item.name }} ALL=(ALL) NOPASSWD:ALL
validate: 'visudo -cf %s' validate: 'visudo -cf %s'
when: item.sudo_nopasswd | default(false) when: item.sudo_nopasswd | default(false)
loop: "{{ users }}" loop: "{{ users }}"