11 Commits
1.0.1 ... 1.0.8

Author SHA1 Message Date
5edb3c08a2 fix(role): Install tmux-resurrect on Arch Linux from AUR 2025-08-21 04:08:51 +02:00
54d2327ec8 fix(role): Make config deletion more robust
We previously attempted to delete a file even if none was present.
This is now fixed.
2025-08-21 04:08:31 +02:00
f122b06697 feat(role): Add package-specific global config files
Packages can now have a global (i.e. system-wide) config defined and
applied. If package is missing the config file is deleted. This is
helpful for example for Git where we want to define helpful aliases
for every logged in user and for tmux where every user should have
access to the tmux-resurrect session manager etc.
2025-08-21 03:48:22 +02:00
588bc932de feat(role): Add package stress-ng
... for load testing hardware
2025-08-10 23:58:25 +02:00
25b59beb72 feat(role): Add package traceroute
... for network debugging and analytics
2025-08-10 23:57:17 +02:00
b07a77fa8f feat(role): Add package debconf-utils to Debian
We'd like to do debconf-get-selections to check contents of the
debconf database.
2025-08-10 23:55:32 +02:00
b599fcc34f feat(role): Add package dhcping
For DHCP client-to-server connection debugging
2025-05-20 19:34:27 +02:00
d73c2e0273 fix(role): Move paru chroot build dir one level up 2025-04-09 03:12:05 +02:00
f018fd5bfc fix(role): Typo 2025-04-07 00:38:32 +02:00
ab2dc0a6d0 fix(role): Explain external var 2025-04-07 00:17:48 +02:00
039b55363f feat(role): Build AUR packages locally in chroot 2025-04-07 00:17:33 +02:00
9 changed files with 334 additions and 26 deletions

View File

@@ -13,8 +13,6 @@ Currently deals exclusively with Linux machines.
# Role Variables # Role Variables
## Defined
Per [defaults/main.yml](defaults/main.yml) this role is equipped to deal with Arch Linux and Debian. Per [defaults/main.yml](defaults/main.yml) this role is equipped to deal with Arch Linux and Debian.
- `packages_linux_common_all_families`: Place package names in this list that are identical across all Linux flavors. - `packages_linux_common_all_families`: Place package names in this list that are identical across all Linux flavors.
@@ -44,12 +42,6 @@ Per [defaults/main.yml](defaults/main.yml) this role is equipped to deal with Ar
- 'paccache-hook' - 'paccache-hook'
``` ```
## Undefined
From somewhere outside of this role the role expects the following variable to be set. This is used in more than one role, we suggest to define this as a host or group var.
- `landscape_quico__e_mail_default_recipient_addr_spec`: An e-mail address such as `alerting@example.com` rendered as an [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.4.1) `addr-spec` string. This is the recipient e-mail address we'll use for system-generated e-mails.
# Dependencies # Dependencies
This role depends on the [kewlfft.aur](https://galaxy.ansible.com/ui/repo/published/kewlfft/aur/) Ansible collection to install packages from Arch User Repository on Arch Linux target machines. This role depends on the [kewlfft.aur](https://galaxy.ansible.com/ui/repo/published/kewlfft/aur/) Ansible collection to install packages from Arch User Repository on Arch Linux target machines.

View File

@@ -3,6 +3,7 @@ packages_linux_common_all_families:
- 'bc' - 'bc'
- 'btop' - 'btop'
- 'dosfstools' - 'dosfstools'
- 'dhcping'
- 'fping' - 'fping'
- 'git' - 'git'
- 'iperf3' - 'iperf3'
@@ -19,9 +20,11 @@ packages_linux_common_all_families:
- 'pigz' - 'pigz'
- 'pv' - 'pv'
- 'rsync' - 'rsync'
- 'stress-ng'
- 'swaks' - 'swaks'
- 'tcpdump' - 'tcpdump'
- 'tmux' - 'tmux'
- 'traceroute'
- 'tree' - 'tree'
- 'unzip' - 'unzip'
- 'wget' - 'wget'
@@ -41,10 +44,12 @@ packages_linux_paru_archlinux:
- 'flent' - 'flent'
- 'mmv' - 'mmv'
- 'paccache-hook' - 'paccache-hook'
- 'tmux-resurrect'
packages_linux_common_debian: packages_linux_common_debian:
- 'apt-file' - 'apt-file'
- 'bind9-dnsutils' - 'bind9-dnsutils'
- 'debconf-utils'
- 'exa' - 'exa'
- 'flent' - 'flent'
- 'gdisk' - 'gdisk'
@@ -52,3 +57,46 @@ packages_linux_common_debian:
- 'netcat-openbsd' - 'netcat-openbsd'
- 'unattended-upgrades' - 'unattended-upgrades'
- 'xxd' - 'xxd'
package_config:
- name: 'tmux'
global_config_file: '/etc/tmux.conf'
marker: 'sane defaults'
global_config: |
# Renumber windows when one is deleted
set-option -g renumber-windows on
# Enable mouse control (clickable windows, panes, resizable panes)
set -g mouse on
- name: 'git'
global_config_file: '/etc/gitconfig'
marker: 'sane defaults'
global_config: |
[alias]
# https://stackoverflow.com/a/30998048
# Find merge that contains a given commit
find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 -s | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
# https://stackoverflow.com/a/30998048
# Show merge commit msg and other details of merge commit
# that contains a given commit
show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"
# https://stackoverflow.com/a/23508223
# Show all commits that are part of a merge commit
log-merge = "!f() { git log --stat \"$1^..$1\"; }; f"
tmux_global_config_file: '/etc/tmux.conf'
tmux_global_config_resurrect: |
# Auto-load tmux-resurrect
# prefix + Ctrl-s - save
# prefix + Ctrl-r - restore
run-shell /usr/share/tmux-resurrect/resurrect.tmux
# Restore pane content (not just running commands)
# This will e.g. show the last 'ls' output you did in a pane
set -g @resurrect-capture-pane-contents 'on'
# Restore a few additional processes beyond the conservative default of
# vi vim nvim emacs man less more tail top htop irssi weechat mutt
set -g @resurrect-processes 'btop journalctl ncdu watch'

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: MIT
[options]
# This 'CacheDir' is the second one in our 'pacman.conf' file once we 'Include =' this file into it. The default dir at
# '/var/cache/pacman/pkg/' is not writable for unprivileged users. Pacman checks for the first writable cache when
# deciding where to store newly built packages. Thus all official packages keep ending up in '/var/cache/pacman/pkg/'
# while all AUR packages automatically end up at '/var/cache/aur/pkg/'.
CacheDir = /var/cache/aur/pkg/
[local-aur]
SigLevel = PackageOptional DatabaseOptional
Server = file:///var/cache/aur/pkg

View File

@@ -0,0 +1,174 @@
# SPDX-License-Identifier: MIT
- name: 'If Arch Linux make sure devtools dependency is installed'
ansible.builtin.package:
name: 'devtools'
state: 'present'
- name: 'If Arch Linux check if ZFS functional'
register: 'role_common_packages__archlinux_zfs_paru_chroot_os_has_functional_zfs'
changed_when: false
failed_when: false
ansible.builtin.shell: |
zpool list
- name: 'If Arch Linux and if ZFS functional check if paru chroot dataset exists'
when: 'role_common_packages__archlinux_zfs_paru_chroot_os_has_functional_zfs.rc == 0'
register: 'role_common_packages__archlinux_zfs_paru_chroot_dataset_present'
changed_when: false
failed_when: false
ansible.builtin.shell: |
zfs list zpool/root/archlinux/var/cache/paru/chroot
- name: 'If Arch Linux and if no paru chroot /dataset/ present check if a chroot /dir/ exists'
when: 'role_common_packages__archlinux_zfs_paru_chroot_dataset_present.rc > 0'
register: 'role_common_packages__archlinux_zfs_paru_chroot_dir_stat'
changed_when: false
failed_when: false
ansible.builtin.stat:
path: '/var/cache/paru/chroot'
- name: 'If Arch Linux and if a paru chroot dir exists move it out of the way'
when: '(role_common_packages__archlinux_zfs_paru_chroot_dataset_present.rc > 0) and (role_common_packages__archlinux_zfs_paru_chroot_dir_stat.stat.isdir is defined and role_common_packages__archlinux_zfs_paru_chroot_dir_stat.stat.isdir)'
register: 'role_common_packages__archlinux_zfs_paru_chroot_dir_was_moved'
changed_when: 'role_common_packages__archlinux_zfs_paru_chroot_dir_was_moved.rc == 0'
ansible.builtin.shell:
cmd: |
mv ''/var/cache/paru/chroot''{,''.bak''}
removes: '/var/cache/paru/chroot'
creates: '/var/cache/paru/chroot.bak'
- name: 'If Arch Linux and if no paru chroot dataset exists create dataset'
when: 'role_common_packages__archlinux_zfs_paru_chroot_dataset_present.rc > 0'
register: 'role_common_packages__archlinux_zfs_paru_chroot_dataset_was_created'
changed_when: 'role_common_packages__archlinux_zfs_paru_chroot_dataset_was_created.rc == 0'
ansible.builtin.shell:
cmd: |
if ! zfs list zpool/root/archlinux/var &>/dev/null; then zfs create -o canmount=off -o space.quico:auto-snapshot=- zpool/root/archlinux/var; fi && \
if ! zfs list zpool/root/archlinux/var/cache &>/dev/null; then zfs create -o canmount=off zpool/root/archlinux/var/cache; fi && \
if ! zfs list zpool/root/archlinux/var/cache/paru &>/dev/null; then zfs create -o canmount=off zpool/root/archlinux/var/cache/paru; fi && \
zfs create zpool/root/archlinux/var/cache/paru/chroot
- name: 'If Arch Linux and if a paru chroot dataset now exists and if paru chroot dir was moved out of the way move it back in place'
when: '(role_common_packages__archlinux_zfs_paru_chroot_dir_was_moved.rc is defined and role_common_packages__archlinux_zfs_paru_chroot_dir_was_moved.rc == 0) and (role_common_packages__archlinux_zfs_paru_chroot_dataset_was_created.rc == 0)'
ansible.builtin.shell:
cmd: |
rsync -a --remove-source-files ''/var/cache/paru/chroot''{''.bak'',}''/'' && \
find ''/var/cache/paru/chroot.bak'' -type d -empty -delete
removes: '/var/cache/paru/chroot.bak'
- name: 'If Arch Linux and if ZFS functional check if AUR package cache dataset exists'
when: 'role_common_packages__archlinux_zfs_paru_chroot_os_has_functional_zfs.rc == 0'
register: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_present'
changed_when: false
failed_when: false
ansible.builtin.shell: |
zfs list zpool/root/archlinux/var/cache/aur/pkg
- name: 'If Arch Linux and if no AUR package cache /dataset/ present check if a chroot /dir/ exists'
when: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_present.rc > 0'
register: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dir_stat'
changed_when: false
failed_when: false
ansible.builtin.stat:
path: '/var/cache/aur/pkg'
- name: 'If Arch Linux and if an AUR package cache dir exists move it out of the way'
when: '(role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_present.rc > 0) and (role_common_packages__archlinux_zfs_aur_pkg_cache_dir_stat.stat.isdir is defined and role_common_packages__archlinux_zfs_aur_pkg_cache_dir_stat.stat.isdir)'
register: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dir_was_moved'
changed_when: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dir_was_moved.rc == 0'
ansible.builtin.shell:
cmd: |
mv ''/var/cache/aur/pkg''{,''.bak''}
removes: '/var/cache/aur/pkg'
creates: '/var/cache/aur/pkg.bak'
- name: 'If Arch Linux and if no AUR package cache dataset exists create dataset'
when: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_present.rc > 0'
register: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_was_created'
changed_when: 'role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_was_created.rc == 0'
ansible.builtin.shell:
cmd: |
if ! zfs list zpool/root/archlinux/var &>/dev/null; then zfs create -o canmount=off -o space.quico:auto-snapshot=- zpool/root/archlinux/var; fi && \
if ! zfs list zpool/root/archlinux/var/cache &>/dev/null; then zfs create -o canmount=off zpool/root/archlinux/var/cache; fi && \
if ! zfs list zpool/root/archlinux/var/cache/aur &>/dev/null; then zfs create -o canmount=off zpool/root/archlinux/var/cache/aur; fi && \
zfs create zpool/root/archlinux/var/cache/aur/pkg
- name: 'If Arch Linux and if a AUR package cache dataset now exists and if AUR package cache dir was moved out of the way move it back in place'
when: '(role_common_packages__archlinux_zfs_aur_pkg_cache_dir_was_moved.rc is defined and role_common_packages__archlinux_zfs_aur_pkg_cache_dir_was_moved.rc == 0) and (role_common_packages__archlinux_zfs_aur_pkg_cache_dataset_was_created.rc == 0)'
ansible.builtin.shell:
cmd: |
rsync -a --remove-source-files ''/var/cache/aur/pkg''{''.bak'',}''/'' && \
find ''/var/cache/aur/pkg.bak'' -type d -empty -delete
removes: '/var/cache/aur/pkg.bak'
- name: 'If Arch Linux set global write perms (0777) on AUR package cache dir ''/var/cache/aur/pkg'''
ansible.builtin.file:
path: '/var/cache/aur/pkg'
mode: '0777'
- name: 'If Arch Linux copy local AUR repo config file'
ansible.builtin.copy:
src: 'etc/pacman.d/repo-local-aur.conf'
dest: '/etc/pacman.d/repo-local-aur.conf'
- name: 'If Arch Linux include local AUR repo config in main pacman config'
loop_control:
loop_var: 'pacman_opt'
index_var: 'i'
label: 'Set ''{{ pacman_opt.name }}'' to ''{{ pacman_opt.value }}'', this {{ pacman_opt.purpose_human_readable }}'
loop:
- { name: 'CacheDir', value: '/var/cache/pacman/pkg/', exclusive: false, purpose_human_readable: 'adds an AUR-specific cache dir (since we make this one user-writable)' }
- { name: 'Include', value: '/etc/pacman.d/repo-local-aur.conf', exclusive: false, purpose_human_readable: 'adds our local AUR repo' }
community.general.ini_file:
path: '/etc/pacman.conf'
section: 'options'
option: '{{ pacman_opt.name }}'
value: '{{ pacman_opt.value }}'
exclusive: '{{ pacman_opt.exclusive }}'
- name: 'If Arch Linux set ''/etc/paru.conf'' settings for local repo chroot AUR builds'
loop_control:
loop_var: 'paru_option'
index_var: 'i'
label: 'Set ''{{ paru_option.name }}'' {% if paru_option.value %}to ''{{ paru_option.value }}''{% else %}without a value{% endif %}, {{ paru_option.purpose_human_readable }}'
loop:
- { name: 'LocalRepo', value: '', allow_no_value: true, purpose_human_readable: 'this builds AUR packages into the local repo(s) specified in ''/etc/pacman.conf''' }
- { name: 'Chroot', value: '/var/cache/paru/chroot', allow_no_value: false, purpose_human_readable: 'this builds AUR packages in a clean chroot' }
community.general.ini_file:
path: '/etc/paru.conf'
section: 'options'
option: '{{ paru_option.name }}'
value: '{{ paru_option.value }}'
allow_no_value: '{{ paru_option.allow_no_value }}'
- name: 'If Arch Linux initialize local AUR repo'
changed_when: false
failed_when: false
ansible.builtin.shell:
cmd: |
paru -Ly
- name: 'If Arch Linux remove package ''paru-bin-debug'''
ansible.builtin.package:
name: 'paru-bin-debug'
state: 'absent'
- name: 'If Arch Linux check if any non-local AUR packages remain to be moved into our local AUR repo'
register: 'role_common_packages__archlinux_zfs_aur_pkg_cache_non_local_pkgs'
changed_when: false
failed_when: false
ansible.builtin.shell:
cmd: |
comm -23 <(paru -Qem | awk '{print $1}' | sort) <(paru -Ll | awk '{ print $2 }' | sort)
- name: 'If Arch Linux and if any non-local AUR packages exist move non-local AUR packages into local AUR repo'
become: 'yes'
become_user: 'build'
loop_control:
loop_var: 'pkg'
index_var: 'i'
label: 'Move pkg into local AUR repo: ''{{ pkg }}'''
loop: '{{ role_common_packages__archlinux_zfs_aur_pkg_cache_non_local_pkgs.stdout_lines }}'
ansible.builtin.shell:
cmd: |
paru -S --noconfirm '{{ pkg }}';

View File

@@ -0,0 +1,41 @@
# SPDX-License-Identifier: MIT
# This file adds config blocks to files that are dependent on a single package
# being present. If for example the 'git' package is present we want content in
# Git's global '/etc/gitconfig' file. If the package is absent that file content
# is removed. If after that the file is empty (i.e. has a size of 0 bytes) the
# file is deleted.
- name: 'Add per-package config'
when: 'pkg.name in ansible_facts.packages'
loop_control:
loop_var: 'pkg'
label: 'If ''{{ pkg.name }}'' package is present set system-wide config in ''{{ pkg.global_config_file }}'''
loop: '{{ package_config }}'
ansible.builtin.blockinfile:
marker: '# {mark} ANSIBLE MANAGED BLOCK - {{ pkg.name }} {{ pkg.marker }}'
path: '{{ pkg.global_config_file }}'
append_newline: true
prepend_newline: true
state: 'present'
create: true
block: '{{ pkg.global_config }}'
- name: 'Get stats of global config files'
register: 'role_common_packages__global_config_file_stats'
loop_control:
loop_var: 'pkg'
label: 'Get stats of ''{{ pkg.name }}'' system-wide config file ''{{ pkg.global_config_file }}'''
loop: '{{ package_config }}'
ansible.builtin.stat:
path: '{{ pkg.global_config_file }}'
- name: 'Remove per-package config'
when: 'stat_result.pkg.name not in ansible_facts.packages and stat_result.stat.exists'
loop_control:
loop_var: 'stat_result'
label: 'If ''{{ stat_result.pkg.name }}'' package is absent and system-wide config file ''{{ stat_result.pkg.global_config_file }}'' is present remove config file'
loop: '{{ role_common_packages__global_config_file_stats.results }}'
ansible.builtin.file:
path: '{{ stat_result.pkg.global_config_file }}'
state: 'absent'

View File

@@ -0,0 +1,31 @@
# SPDX-License-Identifier: MIT
- name: 'If Arch Linux and if ''tmux-resurrect'' and ''tmux'' packages are present auto-load tmux-resurrect on tmux start system-wide'
when: '(ansible_facts[''os_family''] | lower == ''archlinux'') and (''tmux-resurrect'' in ansible_facts.packages) and (''tmux'' in ansible_facts.packages)'
ansible.builtin.blockinfile:
marker: '# {mark} ANSIBLE MANAGED BLOCK - tmux-resurrect'
path: '{{ tmux_global_config_file }}'
append_newline: true
prepend_newline: true
state: 'present'
create: true
block: '{{ tmux_global_config_resurrect }}'
- name: 'Get stats of ''{{ tmux_global_config_file }}'''
ansible.builtin.stat:
path: '{{ tmux_global_config_file }}'
register: 'role_common_packages__tmux_global_config_file_stats'
- name: 'If Arch Linux and ''tmux-resurrect'' package is absent and system-wide config file ''{{ tmux_global_config_file }}'' is present remove config block from file'
when: '(ansible_facts[''os_family''] | lower == ''archlinux'') and (''tmux-resurrect'' not in ansible_facts.packages) and (role_common_packages__tmux_global_config_file_stats.stat.exists)'
ansible.builtin.blockinfile:
marker: '# {mark} ANSIBLE MANAGED BLOCK - tmux-resurrect'
path: '{{ tmux_global_config_file }}'
state: 'absent'
block: '{{ tmux_global_config_resurrect }}'
- name: 'If ''{{ tmux_global_config_file }}'' is 0 bytes delete it'
when: 'role_common_packages__tmux_global_config_file_stats.stat.size == 0'
ansible.builtin.file:
path: '{{ tmux_global_config_file }}'
state: 'absent'

View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT
- import_tasks: 'base-package-auxiliary-settings-tmux.yml'
- import_tasks: 'base-package-auxiliary-settings-single-package-single-config.yml'

23
tasks/base-packages.yml Normal file
View File

@@ -0,0 +1,23 @@
# SPDX-License-Identifier: MIT
- name: 'Gather package facts'
ansible.builtin.package_facts:
manager: 'auto'
- name: 'If OS is a Linux flavor install Linux-specific packages'
when: 'ansible_facts[''system''] | lower == ''linux'''
ansible.builtin.package:
name: '{{ packages_linux_common_all_families }}'
state: 'present'
- name: 'If ''os_family'' is ''{{ ansible_facts[''os_family''] | lower }}'' install {{ ansible_facts[''os_family''] | lower }}-specific packages'
ansible.builtin.package:
name: '{{ vars[''packages_linux_common_'' + ansible_facts[''os_family''] | lower] }}'
state: 'present'
- name: 'If Arch Linux install Arch User Repository (AUR) packages'
when: 'ansible_facts[''os_family''] | lower == ''archlinux'''
kewlfft.aur.aur:
name: '{{ packages_linux_paru_archlinux }}'
state: 'present'
become: 'yes'
become_user: 'build'

View File

@@ -1,22 +1,7 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
- name: 'If OS is a Linux flavor install Linux-specific packages' - import_tasks: 'base-packages.yml'
when: 'ansible_facts[''system''] | lower == ''linux''' - import_tasks: 'base-package-auxiliary-settings.yml'
ansible.builtin.package: - import_tasks: 'arch-linux-local-aur-repo-chroot.yml'
name: '{{ packages_linux_common_all_families }}'
state: 'present'
- name: 'If ''os_family'' is ''{{ ansible_facts[''os_family''] | lower }}'' install {{ ansible_facts[''os_family''] | lower }}-specific packages'
ansible.builtin.package:
name: '{{ vars[''packages_linux_common_'' + ansible_facts[''os_family''] | lower] }}'
state: 'present'
- name: 'If Arch Linux install Arch User Repository (AUR) packages'
when: 'ansible_facts[''os_family''] | lower == ''archlinux''' when: 'ansible_facts[''os_family''] | lower == ''archlinux'''
kewlfft.aur.aur:
name: '{{ packages_linux_paru_archlinux }}'
state: 'present'
become: 'yes'
become_user: 'build'
- import_tasks: 'maintenance-unattended-upgrades.yml' - import_tasks: 'maintenance-unattended-upgrades.yml'
when: 'ansible_facts[''os_family''] | lower == ''debian''' when: 'ansible_facts[''os_family''] | lower == ''debian'''