Compare commits
116 Commits
master
...
696df925c6
| Author | SHA1 | Date | |
|---|---|---|---|
| 696df925c6 | |||
| 65ef8cb1ca | |||
| 396d802dc3 | |||
| 90cc9add01 | |||
| eeaf3b0f0a | |||
| 0a76e07b39 | |||
| 82a1548b2e | |||
| 95b793885a | |||
| f7c020de52 | |||
| 7e4c2d87e2 | |||
| bc6bd2823f | |||
| 01e0ea8b4b | |||
| 75395cc8d2 | |||
| be80c4096c | |||
| f8e3ce62d4 | |||
| 78316a8946 | |||
| 5226206cab | |||
| d9e42c0c84 | |||
| b9484dadab | |||
| 230b14e2ab | |||
| f9a8791b4d | |||
| f46dea0748 | |||
| b1eedd30dc | |||
| 98d0a4954d | |||
| fd37b4ee96 | |||
| 7fe2a0dcc1 | |||
| cc77f646d7 | |||
| 2be6117aac | |||
| 232ab244ca | |||
| ef945d925a | |||
| 366299ea6d | |||
| 3da6894ff1 | |||
| e1db2ce434 | |||
| ae4fb6f43c | |||
| 2c23ce6cbb | |||
| 0211efbae7 | |||
| dda1287f23 | |||
| f62dba3ed6 | |||
| f08855456a | |||
| 4bce08e77b | |||
| 72ec492a33 | |||
| efad1b9a67 | |||
| 732784fa2d | |||
| a71d27c29d | |||
| 7953c2c285 | |||
| 7a1a44220b | |||
| 970af5ff73 | |||
| 035189d326 | |||
| ede6829a89 | |||
| b9156a0cac | |||
| 1c5f93e76f | |||
| fe635b0783 | |||
| 0b4d2320c0 | |||
| 11f7af1d9f | |||
| e3a52b889b | |||
| ff2e5fb6b8 | |||
| db62d360b7 | |||
| 3d3f1caa14 | |||
| 200e73e3ef | |||
| f5fda74cad | |||
| 9e4ae3ae33 | |||
| 052c89aa3e | |||
| 21e6edcf63 | |||
| 4961cc4b03 | |||
| a7497dbb0e | |||
| c764c209cb | |||
| 9096a8fc18 | |||
| 236df77406 | |||
| ba6938b225 | |||
| 919c2085d2 | |||
| 55e7b5e98c | |||
| ef81e6b121 | |||
| 2cf2f71b9c | |||
| 7b972053ef | |||
| 1afe5155ce | |||
| 67065520a2 | |||
| b3b6376d81 | |||
| 9f14556ef6 | |||
| 293b608c84 | |||
| 50a7011de7 | |||
| 8d0c948dff | |||
| 183ec709f6 | |||
| 6dd32b5a63 | |||
| 9fdf83aad3 | |||
| 15fc6e0dd1 | |||
| f866502d47 | |||
| 4291aa8c4a | |||
| 6e8ac0283a | |||
| c650c2b50c | |||
| 2cc06e3f7d | |||
| 8ba12fe4bf | |||
| c72ccd06aa | |||
| bfadc82e82 | |||
| c1b5793cab | |||
| 72dabe3107 | |||
| 0ff03d9d6f | |||
| 247e3e6c3b | |||
| d864a492ee | |||
| 2e7e4d6423 | |||
| 2d96b12367 | |||
| 9f3d638381 | |||
| 88aebd5276 | |||
| 29a493bf13 | |||
| 99e0fb9e5c | |||
| 8618f8cf03 | |||
| ccc53081f4 | |||
| 46b7f56425 | |||
| 3994d4192d | |||
| e22cf5cc60 | |||
| 08a35b2b6b | |||
| e357c7881a | |||
| 10d6095aad | |||
| fcc2ace185 | |||
| e3d61d5fdc | |||
| 1af1ea8ffb | |||
| 9ebfc500a2 |
@@ -1,6 +1,4 @@
|
|||||||
skip_list:
|
skip_list:
|
||||||
- run-once
|
- run-once
|
||||||
- var-naming[no-role-prefix] # user-facing API dicts (cis, system, hypervisor) are intentionally not role-prefixed
|
|
||||||
- args[module] # false positives from variable-based module_defaults (_proxmox_auth, _vmware_auth)
|
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- roles/global_defaults/
|
- roles/global_defaults/
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,6 +6,3 @@ vars.yml
|
|||||||
vars.yaml
|
vars.yaml
|
||||||
vars_kvm.yml
|
vars_kvm.yml
|
||||||
vars_libvirt.yml
|
vars_libvirt.yml
|
||||||
vars_proxmox.yml
|
|
||||||
|
|
||||||
.sisyphus/
|
|
||||||
|
|||||||
19
.yamllint
19
.yamllint
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
extends: default
|
|
||||||
|
|
||||||
rules:
|
|
||||||
document-start: disable
|
|
||||||
line-length:
|
|
||||||
max: 200
|
|
||||||
allow-non-breakable-words: true
|
|
||||||
allow-non-breakable-inline-mappings: true
|
|
||||||
truthy:
|
|
||||||
allowed-values: ["true", "false"]
|
|
||||||
check-keys: false
|
|
||||||
comments:
|
|
||||||
min-spaces-from-content: 1
|
|
||||||
comments-indentation: disable
|
|
||||||
braces:
|
|
||||||
max-spaces-inside: 1
|
|
||||||
octal-values:
|
|
||||||
forbid-implicit-octal: true
|
|
||||||
819
README.md
819
README.md
@@ -1,621 +1,222 @@
|
|||||||
# Ansible Bootstrap
|
# Ansible-Bootstrap
|
||||||
|
|
||||||
Automated Linux system bootstrap using the Arch Linux ISO as a universal installer. Deploys any supported distribution on virtual or physical targets via Infrastructure-as-Code.
|
An Ansible playbook for automating system bootstrap processes in an Infrastructure-as-Code manner.
|
||||||
|
|
||||||
Non-Arch targets require the appropriate package manager available from the ISO environment (e.g. `dnf` for RHEL-family). Set `system.features.chroot.tool` if `arch-chroot` is unavailable.
|
# Info
|
||||||
|
|
||||||
|
Most of the roles are adaptable for use with systems beyond ArchLinux, requiring only that the target system can install a necessary package manager, such as `dnf` for RHEL-based systems. Additionally, a replacement for the `arch-chroot` command may be required for these systems.
|
||||||
|
|
||||||
|
**NOTE**:
|
||||||
|
|
||||||
|
- For RHEL 8, RHEL 9, and RHEL 10, repository access requires the `rhel_iso` variable. This variable specifies a local ISO or proxy repository.
|
||||||
|
- RHEL systems do not support `btrfs`. Use `ext4` or `xfs` as alternatives.
|
||||||
|
- For RHEL 8, `xfs` may cause installation issues; `ext4` is recommended.
|
||||||
|
- `custom_iso: true` skips ArchISO validation and pacman setup, your installer ISO must provide the tools required by the selected roles.
|
||||||
|
|
||||||
|
# Supported Distributions
|
||||||
|
|
||||||
|
This playbook supports multiple Linux distributions with specific versions tailored to each.
|
||||||
|
Below is a list of supported distributions:
|
||||||
|
|
||||||
|
| `os` | Distribution |
|
||||||
|
| ---------- | ---------------------------------- |
|
||||||
|
| archlinux | ArchLinux (Latest rolling release) |
|
||||||
|
| almalinux | AlmaLinux 9.x |
|
||||||
|
| debian11 | Debian 11 (Bullseye) |
|
||||||
|
| debian12 | Debian 12 (Bookworm) |
|
||||||
|
| debian13 | Debian 13 (Trixie) |
|
||||||
|
| fedora | Fedora 43 |
|
||||||
|
| rhel8 | Red Hat Enterprise Linux 8 |
|
||||||
|
| rhel9 | Red Hat Enterprise Linux 9 |
|
||||||
|
| rhel10 | Red Hat Enterprise Linux 10 |
|
||||||
|
| rocky | Rocky Linux 9.x |
|
||||||
|
| ubuntu | Ubuntu 25.04 (Plucky Puffin) |
|
||||||
|
| ubuntu-lts | Ubuntu 24.04 LTS (Noble Numbat) |
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Supported Platforms](#1-supported-platforms)
|
1. [Overview](#1-overview)
|
||||||
2. [Compatibility Notes](#2-compatibility-notes)
|
2. [Global Variables](#2-global-variables)
|
||||||
3. [Configuration Model](#3-configuration-model)
|
3. [Inventory Variables](#3-inventory-variables)
|
||||||
4. [Variable Reference](#4-variable-reference)
|
4. [How to Use the Playbook](#4-how-to-use-the-playbook)
|
||||||
- 4.1 [Core Variables](#41-core-variables)
|
- 4.1 [Prerequisites](#41-prerequisites)
|
||||||
- 4.2 [`system` Dictionary](#42-system-dictionary)
|
- 4.2 [Running the Playbook](#42-running-the-playbook)
|
||||||
- 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary)
|
- 4.3 [Example Usage](#43-example-usage)
|
||||||
- 4.4 [CIS Hardening](#44-cis-hardening)
|
|
||||||
- 4.5 [VMware Guest Operations](#45-vmware-guest-operations)
|
## 1. Overview
|
||||||
- 4.6 [Multi-Disk Schema](#46-multi-disk-schema)
|
|
||||||
- 4.7 [Advanced Partitioning Overrides](#47-advanced-partitioning-overrides)
|
The playbook uses the ArchLinux ISO as a foundational tool to provides an efficient and systematic method for the automatic deployment of a variety of Linux distributions on designated target systems. It ensures a standardized setup across different platforms, equipping each system with the essential configurations and software necessary for its designated role.
|
||||||
- 4.8 [Cleanup Defaults](#48-cleanup-defaults)
|
|
||||||
5. [Execution Pipeline](#5-execution-pipeline)
|
## 2. Global Variables
|
||||||
6. [Usage](#6-usage)
|
|
||||||
7. [Security](#7-security)
|
Global variables apply across your Ansible project and can be supplied via inventory or `-e @vars_example.yml`. These variables define common settings such as hypervisor connection details and the boot ISO path. They can be overridden by inventory variables for specific hosts or VMs if needed.
|
||||||
8. [Safety](#8-safety)
|
|
||||||
|
### 2.1 Core Provisioning
|
||||||
## 1. Supported Platforms
|
|
||||||
|
| Variable | Description | Example Value |
|
||||||
### Distributions
|
| ----------------------- | ---------------------------------------------------------- | ----------------------------------------- |
|
||||||
|
| `install_type` | Type of installation. | `virtual`, `physical` |
|
||||||
| `system.os` | Distribution | `system.version` |
|
| `hypervisor` | Type of hypervisor (required for virtual installs). | `libvirt`, `proxmox`, `vmware`, `none` |
|
||||||
| ------------ | ------------------------ | ------------------------------------- |
|
| `install_drive` | Drive where the system will be installed. | `/dev/sda` |
|
||||||
| `almalinux` | AlmaLinux | `9`, `10` |
|
| `boot_iso` | Path to the boot ISO image. | `local-btrfs:iso/archlinux-x86_64.iso` |
|
||||||
| `archlinux` | Arch Linux | latest (rolling) |
|
| `rhel_iso` | Path to the RHEL ISO file, required for RHEL 8/9/10. | `local-btrfs:iso/rhel-9.4-x86_64-dvd.iso` |
|
||||||
| `debian` | Debian | `12`, `13`, `unstable` |
|
| `custom_iso` (optional) | Skip ArchISO checks and pacman setup on installer media. | `true`, `false (default)` |
|
||||||
| `fedora` | Fedora | `43`, `44` |
|
| `cis` (optional) | Adjusts the installation to be CIS level 3 conformant. | `true`, `false (default)` |
|
||||||
| `rhel` | Red Hat Enterprise Linux | `9`, `10` |
|
| `selinux` (optional) | Toggle SELinux where supported. | `true (default)`, `false` |
|
||||||
| `rocky` | Rocky Linux | `9`, `10` |
|
| `firewalld_enabled` (optional) | Toggle firewalld package/service enablement. | `true (default)`, `false` |
|
||||||
| `ubuntu` | Ubuntu (latest non-LTS) | optional (tracks 25.10 `questing`) |
|
|
||||||
| `ubuntu-lts` | Ubuntu LTS | optional (tracks 26.04 `resolute`) |
|
### 2.2 Hypervisor Access (virtual installs)
|
||||||
|
|
||||||
### Hypervisors
|
| Variable | Description | Example Value |
|
||||||
|
| ----------------------- | ---------------------------------------------------------- | -------------------- |
|
||||||
| Hypervisor | `hypervisor.type` |
|
| `hypervisor_url` | URL/IP address for the hypervisor interface. | `192.168.0.2` |
|
||||||
| ----------- | ----------------- |
|
| `hypervisor_username` | Username for hypervisor authentication. | `root@pam` |
|
||||||
| libvirt | `libvirt` |
|
| `hypervisor_password` | Password for hypervisor authentication. | `123456` |
|
||||||
| Proxmox VE | `proxmox` |
|
| `hypervisor_datacenter` | Name of the hypervisor datacenter. | `default-datacenter` |
|
||||||
| VMware | `vmware` |
|
| `hypervisor_cluster` | Name of the hypervisor cluster. | `default-cluster` |
|
||||||
| Xen | `xen` |
|
| `hypervisor_node` | Hypervisor node name. | `node01` |
|
||||||
| Bare metal | `none` |
|
| `hypervisor_storage` | Storage identifier for VM disks. | `local-btrfs` |
|
||||||
|
| `vm_path` (optional) | Libvirt image dir or VMware folder path. | `/var/lib/libvirt/images` |
|
||||||
## 2. Compatibility Notes
|
| `vmware_ssh` | If Ansible should use SSH after base VMware setup. | `true`, `false (default)` |
|
||||||
|
| `vlan_name` (optional) | VLAN for the VM's network interface. | `vlan100` |
|
||||||
- `rhel_iso` is required for `system.os: rhel`.
|
| `note` (optional) | VMware VM annotation. | `Provisioned by Ansible` |
|
||||||
- RHEL installs should use `ext4` or `xfs` (not `btrfs`).
|
|
||||||
- `custom_iso: true` skips ArchISO validation; your installer must provide required tooling.
|
### 2.3 VMware Tools connection (VMware installs)
|
||||||
- On non-Arch installers, set `system.features.chroot.tool` explicitly.
|
|
||||||
|
These are required when `hypervisor: vmware` uses the `vmware_tools` connection.
|
||||||
## 3. Configuration Model
|
|
||||||
|
| Variable | Description | Example Value |
|
||||||
Two dict-based variables drive the entire configuration:
|
| ------------------------------- | ------------------------------------------ | -------------------------------------- |
|
||||||
|
| `ansible_vmware_tools_user` | Guest OS user for guest operations. | `root` |
|
||||||
- **`system`** -- host, network, users, disk layout, encryption, and feature toggles (including CIS hardening under `system.features.cis`)
|
| `ansible_vmware_tools_password` | Guest OS password for guest operations. | `""` |
|
||||||
- **`hypervisor`** -- virtualization backend credentials and targeting
|
| `ansible_vmware_guest_path` | VM inventory path (datacenter + folder). | `/dc01/vm/Folder/vm01.example.com` |
|
||||||
|
| `ansible_vmware_host` | vCenter/ESXi hostname. | `vcenter01.example.com` |
|
||||||
Both are standard Ansible variables. Place them in `group_vars/`, `host_vars/`, or inline inventory. With `hash_behaviour = merge`, dictionaries merge across scopes, so shared values go in group vars and host-specific overrides go per-host.
|
| `ansible_vmware_user` | vCenter/ESXi username. | `administrator@vsphere.local` |
|
||||||
|
| `ansible_vmware_password` | vCenter/ESXi password. | `********` |
|
||||||
### Variable Placement
|
| `ansible_vmware_validate_certs` | Validate vCenter/ESXi TLS certs. | `false` |
|
||||||
|
|
||||||
| Location | Scope | Typical use |
|
### 2.4 Disk Encryption (optional)
|
||||||
| ------------------------ | ----------- | -------------------------------------------------------------- |
|
|
||||||
| `group_vars/all.yml` | All hosts | Shared `hypervisor`, `system.filesystem`, `boot_iso` |
|
| Variable | Description | Example Value |
|
||||||
| `group_vars/<group>.yml` | Group | Environment-specific defaults |
|
| -------------------------- | ----------------------------------------------- | ------------------ |
|
||||||
| `host_vars/<host>.yml` | Single host | Host-specific overrides (`system.network.ip`, `system.id`, etc.) |
|
| `luks_enabled` | Enable LUKS encryption for the root volume. | `true`, `false` |
|
||||||
|
| `luks_passphrase` | Passphrase used for initial LUKS format/unlock. | `1234` |
|
||||||
### Example Inventory
|
| `luks_mapper_name` | Decrypted mapper name. | `SYSTEM_DECRYPTED` |
|
||||||
|
| `luks_auto_decrypt` | Enable automatic unlock on boot. | `true`, `false` |
|
||||||
```yaml
|
| `luks_auto_decrypt_method` | Auto-unlock method. | `tpm2`, `keyfile`, `manual` |
|
||||||
all:
|
| `luks_tpm2_device` | TPM2 device for enrollment. | `auto` |
|
||||||
vars:
|
| `luks_tpm2_pcrs` | TPM2 PCR list (systemd-cryptenroll). | `7` |
|
||||||
system:
|
| `luks_keyfile_size` | Keyfile size in bytes for initramfs. | `64` |
|
||||||
filesystem: btrfs
|
| `luks_options` | LUKS options passed to crypttab/kernel. | `discard,tries=3` |
|
||||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
| `luks_type` | LUKS format type. | `luks2` |
|
||||||
hypervisor:
|
| `luks_cipher` | LUKS cipher. | `aes-xts-plain64` |
|
||||||
type: proxmox
|
| `luks_hash` | LUKS hash. | `sha512` |
|
||||||
url: pve01.example.com
|
| `luks_iter_time` | LUKS iter time in milliseconds. | `4000` |
|
||||||
username: root@pam
|
| `luks_key_size` | LUKS key size in bits. | `512` |
|
||||||
password: !vault |
|
| `luks_pbkdf` | LUKS PBKDF algorithm. | `argon2id` |
|
||||||
$ANSIBLE_VAULT...
|
| `luks_use_urandom` | Reserved; module uses cryptsetup defaults. | `true` |
|
||||||
node: pve01
|
| `luks_verify_passphrase` | Reserved; module uses cryptsetup defaults. | `true` |
|
||||||
storage: local-lvm
|
|
||||||
|
### 2.5 Partitioning Overrides (advanced)
|
||||||
children:
|
|
||||||
bootstrap:
|
Use these only when you need to override the default layout logic.
|
||||||
hosts:
|
|
||||||
app01.example.com:
|
| Variable | Description | Example Value |
|
||||||
ansible_host: 10.0.0.10
|
| ---------------------------- | -------------------------------------------------------- | ------------- |
|
||||||
system:
|
| `partitioning_efi_size_mib` | ESP size in MiB. | `512` |
|
||||||
type: virtual
|
| `partitioning_boot_size_mib` | `/boot` size in MiB when a separate boot is used. | `1024` |
|
||||||
os: debian
|
| `partitioning_separate_boot` | Force a separate `/boot` partition. | `true` |
|
||||||
version: "12"
|
| `partitioning_boot_fs_fstype` | Filesystem for `/boot` when separate. | `ext4` |
|
||||||
name: app01.example.com
|
| `partitioning_use_full_disk` | Use remaining LVM space for the root volume. | `true` |
|
||||||
id: 101
|
|
||||||
cpus: 2
|
To protect sensitive information, such as passwords, API keys, and other confidential variables (e.g., `hypervisor_password`), **it is recommended to use Ansible Vault**.
|
||||||
memory: 4096
|
|
||||||
network:
|
## 3. Inventory Variables
|
||||||
bridge: vmbr0
|
|
||||||
ip: 10.0.0.10
|
Inventory variables are defined for individual hosts or VMs in the inventory file, allowing customization of settings such as the operating system, filesystem, and compliance with CIS benchmarks. These variables can be set globally and overridden for specific hosts or VMs.
|
||||||
prefix: 24
|
|
||||||
gateway: 10.0.0.1
|
### 3.1 System Identity and OS
|
||||||
dns:
|
|
||||||
servers: [1.1.1.1, 1.0.0.1]
|
| Variable | Description | Example Value |
|
||||||
search: [example.com]
|
| ------------ | -------------------------------------- | ---------------------- |
|
||||||
disks:
|
| `ansible_host` | Ansible connection address for the host. | `192.168.0.10` |
|
||||||
- size: 40
|
| `os` | Operating system to be installed. | `ubuntu-lts` |
|
||||||
- size: 120
|
| `filesystem` | Filesystem type for the root volume. | `btrfs`, `ext4`, `xfs` |
|
||||||
mount:
|
| `hostname` | The hostname assigned to the system. | `vm01` |
|
||||||
path: /data
|
|
||||||
fstype: xfs
|
### 3.2 Credentials and Access
|
||||||
users:
|
|
||||||
ops:
|
These are prompted by default via `vars_prompt` in `main.yml`, but can be supplied via inventory/vars/`-e` for non-interactive runs.
|
||||||
password: !vault |
|
|
||||||
$ANSIBLE_VAULT...
|
| Variable | Description | Example Value |
|
||||||
keys:
|
| ----------------- | ---------------------------------- | ----------------- |
|
||||||
- "ssh-ed25519 AAAA..."
|
| `root_password` | Root password (vault recommended). | `SecurePass123` |
|
||||||
sudo: true
|
| `user_name` | Username for a user account. | `adminuser` |
|
||||||
root:
|
| `user_password` | Password for the user account. | `UserPass123` |
|
||||||
password: !vault |
|
| `user_public_key` | SSH Key for the user account. | `ssh-ed25519 AAAA` |
|
||||||
$ANSIBLE_VAULT...
|
|
||||||
luks:
|
### 3.3 Networking
|
||||||
enabled: true
|
|
||||||
passphrase: !vault |
|
| Variable | Description | Example Value |
|
||||||
$ANSIBLE_VAULT...
|
| --------------- | -------------------------------------------------------------- | ----------------- |
|
||||||
method: tpm2
|
| `vm_ip` | IP address assigned to the system (omit to use DHCP). | `192.168.0.10` |
|
||||||
tpm2:
|
| `vm_nms` | Netmask bits for static addressing. | `24` |
|
||||||
pcrs: "7"
|
| `vm_gw` | Default gateway IP address (static only). | `192.168.0.1` |
|
||||||
features:
|
| `vm_dns` | DNS server IP address(es). | `1.0.0.1,1.1.1.1` |
|
||||||
cis:
|
| `vm_dns_search` | DNS search zone(s) for the network configuration. | `example.com` |
|
||||||
enabled: true
|
| `vm_nif` | Network interface/bridge for the VM's network connection. | `vmbr0` |
|
||||||
firewall:
|
|
||||||
enabled: true
|
### 3.4 VM Sizing (virtual installs)
|
||||||
backend: firewalld
|
|
||||||
toolkit: nftables
|
| Variable | Description | Example Value |
|
||||||
```
|
| ----------- | --------------------------------- | ------------- |
|
||||||
|
| `vm_id` | Unique identifier for the VM. | `101` |
|
||||||
## 4. Variable Reference
|
| `vm_size` | Disk size allocated in GB (min 20). | `20` |
|
||||||
|
| `vm_memory` | Amount of memory in MB. | `2048` |
|
||||||
### 4.1 Core Variables
|
| `vm_cpus` | Number of CPU cores (virtual installs). | `4` |
|
||||||
|
| `vm_ballo` | Ballooning memory size (optional).| `2048` |
|
||||||
Top-level variables outside `system`/`hypervisor`.
|
|
||||||
|
### 3.5 Post-install Packages
|
||||||
| Variable | Type | Default | Description |
|
|
||||||
| ---------------- | ------ | -------------------------- | ---------------------------------------------------- |
|
| Variable | Description | Example Value |
|
||||||
| `boot_iso` | string | -- | Boot ISO path (required for virtual installs) |
|
| ------------------------ | --------------------------------------------------------------------- | ------------------ |
|
||||||
| `rhel_iso` | string | -- | RHEL ISO path (required when `system.os: rhel`) |
|
| `extra_packages` (optional) | Additional packages installed after the first boot into the installed OS. | `["git", "jq"]` |
|
||||||
| `custom_iso` | bool | `false` | Skip ArchISO validation and pacman setup |
|
|
||||||
| `thirdparty_tasks` | string | `dropins/preparation.yml` | Drop-in task file included during environment setup |
|
## 4. How to Use the Playbook
|
||||||
|
|
||||||
### 4.2 `system` Dictionary
|
### 4.1 Prerequisites
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
Before running the playbook, ensure you have Ansible installed and configured correctly, and your inventory file is set up with the target systems defined.
|
||||||
| ------------ | ---------- | ------------------ | ------------------------------------------------------ |
|
|
||||||
| `type` | string | `virtual` | `virtual` or `physical` |
|
### 4.2 Running the Playbook
|
||||||
| `os` | string | -- | Target distribution (see [table](#distributions)) |
|
|
||||||
| `version` | string | -- | Version selector for versioned distros |
|
Execute the playbook using the `ansible-playbook` command, ensuring that all necessary variables are defined, typically by specifying a vars file (such as `vars_example.yml`) containing the required configurations.
|
||||||
| `filesystem` | string | `ext4` | `btrfs`, `ext4`, or `xfs` |
|
|
||||||
| `name` | string | inventory hostname | Final hostname |
|
### 4.3 Example Usage
|
||||||
| `timezone` | string | `Europe/Vienna` | System timezone (tz database name) |
|
|
||||||
| `locale` | string | `en_US.UTF-8` | System locale |
|
An effective way to use the playbook involves defining all necessary configurations within a vars file (for example, `vars_example.yml`). This file should include all relevant global variables tailored to your specific deployment requirements. Additionally, you should prepare an inventory file (`inventory.yml`) that lists all the hosts along with any specific inventory variables they might need. Then, you can run the playbook as follows:
|
||||||
| `keymap` | string | `us` | Console keymap |
|
|
||||||
| `id` | int/string | -- | VMID (required for Proxmox) |
|
|
||||||
| `cpus` | int | `0` | vCPU count (required for virtual) |
|
|
||||||
| `memory` | int | `0` | Memory in MiB (required for virtual) |
|
|
||||||
| `balloon` | int | `0` | Balloon memory in MiB (Proxmox) |
|
|
||||||
| `path` | string | -- | Hypervisor folder/path (falls back to `hypervisor.folder`) |
|
|
||||||
| `content` | dict | see below | Package content source (mirror/DVD/Satellite, family-resolved) |
|
|
||||||
| `packages` | list | `[]` | Additional packages installed post-reboot |
|
|
||||||
| `network` | dict | see below | Network configuration |
|
|
||||||
| `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#46-multi-disk-schema)) |
|
|
||||||
| `users` | dict | `{}` | User accounts (keyed by username) |
|
|
||||||
| `root` | dict | see below | Root account settings |
|
|
||||||
| `luks` | dict | see below | Encryption settings |
|
|
||||||
| `features` | dict | see below | Feature toggles |
|
|
||||||
|
|
||||||
#### `system.content`
|
|
||||||
|
|
||||||
Uniform package content source, family-resolved. `source: ''` defaults to `dvd` on EL and `mirror` on Debian/Ubuntu/Arch. Satellite values come from inventory/vault only, never committed code.
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| -------------------------- | ------ | -------------- | ----------------------------------------------------------------- |
|
|
||||||
| `source` | string | family default | `dvd`, `mirror`, `satellite`, or `none` |
|
|
||||||
| `url` | string | family default | Mirror URL / EL `.repo` baseurl |
|
|
||||||
| `proxy` | string | -- | `http://host:port` content proxy (dnf/apt/pacman) |
|
|
||||||
| `gpgcheck` | bool | `true` | Repository GPG checking |
|
|
||||||
| `satellite.host` | string | -- | EL Katello/Satellite hostname |
|
|
||||||
| `satellite.ip` | string | -- | Optional `/etc/hosts` entry when DNS does not resolve the host |
|
|
||||||
| `satellite.org` | string | -- | Organization label |
|
|
||||||
| `satellite.activation_key` | string | -- | Activation key |
|
|
||||||
| `satellite.ca_url` | string | derived | Katello CA RPM URL (default `https://<host>/pub/katello-ca-consumer-latest.noarch.rpm`) |
|
|
||||||
| `satellite.service_level` | string | -- | syspurpose service level |
|
|
||||||
| `satellite.environment` | string | -- | Lifecycle environment |
|
|
||||||
| `satellite.install` | bool | `false` | `false`: base from DVD/mirror then register; `true`: install from Satellite |
|
|
||||||
|
|
||||||
#### `system.network`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| -------------- | ---------- | ------- | ---------------------------------------------- |
|
|
||||||
| `bridge` | string | -- | Hypervisor network/bridge name |
|
|
||||||
| `vlan` | string/int | -- | VLAN tag |
|
|
||||||
| `ip` | string | -- | Static IP (omit for DHCP) |
|
|
||||||
| `prefix` | int | -- | CIDR prefix (1-32, required with `ip`) |
|
|
||||||
| `gateway` | string | -- | Default gateway |
|
|
||||||
| `dns.servers` | list | `[]` | DNS resolvers (must be a YAML list) |
|
|
||||||
| `dns.search` | list | `[]` | Search domains (must be a YAML list) |
|
|
||||||
| `interfaces` | list | `[]` | Multi-NIC config (overrides flat fields above) |
|
|
||||||
|
|
||||||
When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway`, `vlan`) are auto-wrapped into a single-entry list. When `interfaces` is set, it takes precedence. Each entry supports: `name`, `bridge` (required), `vlan`, `ip`, `prefix`, `gateway`.
|
|
||||||
|
|
||||||
#### `system.users`
|
|
||||||
|
|
||||||
Dict keyed by username. At least one user must have a `password` (used for SSH access during bootstrap). Users without a password get locked accounts (key-only auth).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
users:
|
|
||||||
svcansible:
|
|
||||||
password: "vault_lookup"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
appuser:
|
|
||||||
sudo: "ALL=(ALL) NOPASSWD: ALL"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 BBBB..."
|
|
||||||
```
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ---------- | ----------- | ------- | -------------------------------------------------- |
|
|
||||||
| *(dict key)* | string | -- | Username (required) |
|
|
||||||
| `password` | string | -- | User password (required for at least one user) |
|
|
||||||
| `keys` | list | `[]` | SSH public keys |
|
|
||||||
| `sudo` | bool/string | -- | `true` for NOPASSWD ALL, or custom sudoers string |
|
|
||||||
|
|
||||||
Users must be defined in inventory. The dict format enables additive merging across inventory layers with `hash_behaviour=merge`.
|
|
||||||
|
|
||||||
#### `system.root`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ---------- | ------ | ----------- | ------------- |
|
|
||||||
| `password` | string | -- | Root password |
|
|
||||||
| `shell` | string | `/bin/bash` | Login shell |
|
|
||||||
|
|
||||||
#### `system.luks`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ------------ | ------ | ------------------ | ------------------------------------------ |
|
|
||||||
| `enabled` | bool | `false` | Enable encrypted root |
|
|
||||||
| `passphrase` | string | -- | Passphrase for format/open/enroll |
|
|
||||||
| `mapper` | string | `SYSTEM_DECRYPTED` | Mapper name under `/dev/mapper` |
|
|
||||||
| `auto` | bool | `true` | Auto-unlock toggle |
|
|
||||||
| `method` | string | `tpm2` | Auto-unlock backend: `tpm2` or `keyfile` |
|
|
||||||
| `keysize` | int | `64` | Keyfile size in bytes |
|
|
||||||
| `options` | string | `discard,tries=3` | Additional crypttab options |
|
|
||||||
| `type` | string | `luks2` | LUKS format type |
|
|
||||||
| `cipher` | string | `aes-xts-plain64` | Cipher |
|
|
||||||
| `hash` | string | `sha512` | Hash algorithm |
|
|
||||||
| `iter` | int | `4000` | PBKDF iteration time (ms) |
|
|
||||||
| `bits` | int | `512` | Key size (bits) |
|
|
||||||
| `pbkdf` | string | `argon2id` | PBKDF algorithm |
|
|
||||||
|
|
||||||
#### `system.luks.tpm2`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| -------- | ------------- | ------- | ---------------------------------------------- |
|
|
||||||
| `device` | string | `auto` | TPM2 device selector |
|
|
||||||
| `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`); empty = no PCR binding |
|
|
||||||
|
|
||||||
**TPM2 auto-unlock:** Uses `systemd-cryptenroll` on all distros. The user-set passphrase
|
|
||||||
remains as a backup unlock method. TPM2 enrollment runs in the chroot during bootstrap;
|
|
||||||
if it fails (e.g. no TPM2 hardware), the system boots with passphrase-only unlock and
|
|
||||||
TPM2 can be enrolled post-deployment via `systemd-cryptenroll --tpm2-device=auto <device>`.
|
|
||||||
|
|
||||||
On Debian/Ubuntu, TPM2 auto-unlock requires dracut (initramfs-tools does not support `tpm2-device`).
|
|
||||||
The bootstrap auto-switches to dracut when `method: tpm2` is set. Override via `features.initramfs.generator`.
|
|
||||||
|
|
||||||
#### `system.features`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ------------------ | ------ | -------------- | ------------------------------------ |
|
|
||||||
| `cis.enabled` | bool | `false` | Enable CIS hardening (see [4.4](#44-cis-hardening)) |
|
|
||||||
| `cis.profile` | string | `default` | CIS profile: `default`, `l1`, or `l2` (see [4.4](#44-cis-hardening)) |
|
|
||||||
| `cis.rules` | dict | `{}` | Per-rule CIS overrides |
|
|
||||||
| `cis.params` | dict | `{}` | CIS parameter overrides |
|
|
||||||
| `selinux.enabled` | bool | `true` | SELinux management |
|
|
||||||
| `firewall.enabled` | bool | `true` | Firewall setup |
|
|
||||||
| `firewall.backend` | string | `firewalld` | `firewalld` or `ufw` |
|
|
||||||
| `firewall.toolkit` | string | `nftables` | `nftables` or `iptables` |
|
|
||||||
| `ssh.enabled` | bool | `true` | SSH service/package management |
|
|
||||||
| `zstd.enabled` | bool | `true` | zstd-related tuning |
|
|
||||||
| `swap.enabled` | bool | `true` | Swap setup |
|
|
||||||
| `banner.motd` | bool | `false` | MOTD banner |
|
|
||||||
| `banner.sudo` | bool | `true` | Sudo banner |
|
|
||||||
| `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, or `systemd-nspawn` |
|
|
||||||
| `initramfs.generator` | string | auto-detected | Override initramfs generator (see below) |
|
|
||||||
| `secure_boot.enabled` | bool | `false` | Enable Secure Boot (Arch via sbctl, others via shim) |
|
|
||||||
| `secure_boot.method` | string | -- | Arch only: `sbctl` (default) or `uki` |
|
|
||||||
| `desktop.*` | dict | see below | Desktop environment settings (see [4.2.5](#425-systemfeaturesdesktop)) |
|
|
||||||
| `firmware.*` | dict | see below | Vendor firmware blobs and CPU microcode (see [4.2.6](#426-systemfeaturesfirmware)) |
|
|
||||||
| `gpu.*` | dict | see below | Mesa/Vulkan and per-vendor GPU userspace (see [4.2.7](#427-systemfeaturesgpu)) |
|
|
||||||
| `peripherals.*` | dict | see below | Fingerprint, camera, audio, bluetooth, DisplayLink (see [4.2.8](#428-systemfeaturesperipherals)) |
|
|
||||||
| `hardware.*` | dict | see below | Hardware-detection profile override (see [4.2.9](#429-systemfeatureshardware)) |
|
|
||||||
|
|
||||||
**Initramfs generator auto-detection:** RedHat -> dracut, Arch -> mkinitcpio, Debian/Ubuntu -> initramfs-tools.
|
|
||||||
Override with `dracut`, `mkinitcpio`, or `initramfs-tools`. When LUKS TPM2 auto-unlock is enabled and the
|
|
||||||
native generator does not support `tpm2-device`, the generator is automatically upgraded to dracut.
|
|
||||||
On distros with older dracut (no `tpm2-tss` module), clevis is used as a fallback for TPM2 binding.
|
|
||||||
|
|
||||||
#### 4.2.5 `system.features.desktop`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ----------------- | ------ | -------------- | ----------------------------------------- |
|
|
||||||
| `enabled` | bool | `false` | Install desktop environment |
|
|
||||||
| `environment` | string | `""` | `gnome`, `kde`, `sway`, or `hyprland` |
|
|
||||||
| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, `plasma-login-manager`, `greetd`, or `ly` |
|
|
||||||
| `autologin` | bool \| string | `false` | `false` to disable, or a username from `system.users` to auto-login that user |
|
|
||||||
| `session` | string | auto-from-environment | Session to autologin into; overrides the per-environment default (sddm `.desktop` basename / greetd command) |
|
|
||||||
| `groups` | list | `[]` | Opt-in package groups installed on top of the base set (keys of `desktop_package_groups`, e.g. `dev`) |
|
|
||||||
|
|
||||||
All desktop environments are Wayland-only. `sway` and `hyprland` are available on Arch only;
|
|
||||||
`gnome` and `kde` are available on all three families. On enterprise Linux
|
|
||||||
(almalinux/rocky/rhel) the base desktop installs browser, PDF and image viewers but no
|
|
||||||
video player - none is packaged in the EL base repositories, and no third-party repo is
|
|
||||||
pulled in; add one from rpmfusion/flatpak if you need it.
|
|
||||||
|
|
||||||
When `enabled: true`, the bootstrap installs the desktop environment packages, enables the display manager
|
|
||||||
and bluetooth services, and sets the systemd default target to `graphical.target`.
|
|
||||||
|
|
||||||
Display manager auto-detection: gnome to gdm; kde to plasma-login-manager on Arch and
|
|
||||||
Fedora 44+ (Plasma 6.6), else sddm; sway and hyprland to greetd.
|
|
||||||
|
|
||||||
`ly` is an explicit-only override (never auto-selected), available on Arch only,
|
|
||||||
and is desktop-agnostic - it can front any environment. It runs on `tty2` with
|
|
||||||
`getty@tty2` masked, and its autologin is written to `/etc/ly/config.ini`; set `session`
|
|
||||||
to the target session's `.desktop` basename (sway and hyprland resolve automatically).
|
|
||||||
|
|
||||||
When `autologin` names a user, the matching display manager is configured to log that user in without a
|
|
||||||
password prompt. `session` is resolved automatically per environment when left empty (gdm picks its default,
|
|
||||||
sddm uses `plasma.desktop` for kde, greetd runs the compositor command for sway/hyprland), so it only needs
|
|
||||||
setting to override that choice.
|
|
||||||
|
|
||||||
#### 4.2.6 `system.features.firmware`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ----------- | --------------- | ------- | ----------------------------------------------------------------- |
|
|
||||||
| `enabled` | bool \| `auto` | `auto` | Install vendor firmware blobs. `auto` = on for `physical`, off for `virtual` |
|
|
||||||
| `microcode` | bool \| `auto` | `auto` | Install CPU microcode. `auto` follows `firmware.enabled` |
|
|
||||||
|
|
||||||
Defaults are designed so a baremetal install picks up firmware automatically with no inventory entry needed,
|
|
||||||
while VMs skip it (the hypervisor handles those). The environment role detects CPU/GPU/wireless vendors from
|
|
||||||
the live host (via `lscpu` and `lspci`) and the bootstrap role installs only the matching firmware packages.
|
|
||||||
On Arch, this uses the vendor splits (`linux-firmware-amdgpu`, `linux-firmware-realtek`, etc.) so the install
|
|
||||||
stays minimal. On Debian, it uses the equivalent `firmware-*` packages. Distros without firmware splits fall
|
|
||||||
back to a single meta package.
|
|
||||||
|
|
||||||
#### 4.2.7 `system.features.gpu`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| --------------- | ------ | ------- | ---------------------------------------------------- |
|
|
||||||
| `enabled` | bool | `false` | Install Mesa, Vulkan, and per-GPU userspace |
|
|
||||||
| `nvidia_driver` | string | `auto` | One of `auto`, `open`, `proprietary`, `nouveau` |
|
|
||||||
|
|
||||||
Pair with `desktop.enabled: true` for a working desktop. The package set is determined by the same hardware
|
|
||||||
profile as `firmware`. The `nvidia_driver: auto` default picks **`open`** (`nvidia-open` kernel modules) for
|
|
||||||
Turing or newer GPUs, falls back to **`proprietary`** for older cards on distros that ship the proprietary
|
|
||||||
driver, and falls back to **`nouveau`** elsewhere. Force a specific flavor by setting the value explicitly.
|
|
||||||
|
|
||||||
Proprietary and open Nvidia drivers on Fedora require RPMFusion non-free, which the bootstrap enables
|
|
||||||
automatically when needed. Debian uses `nvidia-driver` from the `non-free` component (already enabled in the
|
|
||||||
managed `sources.list`). Ubuntu uses `restricted`. Arch ships both `nvidia-open-dkms` and `nvidia-dkms` in
|
|
||||||
the `extra` repository - no third-party setup required.
|
|
||||||
|
|
||||||
> **Known limitation - Nvidia on Enterprise Linux (AlmaLinux/Rocky/RHEL):** the EL `akmod-nvidia*`
|
|
||||||
> packages live in RPMFusion non-free, and the bootstrap only enables RPMFusion automatically on
|
|
||||||
> **Fedora**, not on EL. So Nvidia on a bare EL desktop is best-effort: enable RPMFusion (or supply the
|
|
||||||
> driver repo) out of band, or it falls back to `nouveau`. EL desktops are not a primary target.
|
|
||||||
|
|
||||||
#### 4.2.8 `system.features.peripherals`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ------------- | --------------- | ------- | ---------------------------------------------------------- |
|
|
||||||
| `enabled` | bool \| `auto` | `auto` | Master switch. `auto` follows `desktop.enabled` |
|
|
||||||
| `fingerprint` | bool \| `auto` | `auto` | `fprintd`/`libfprint`. `auto` = install when reader detected |
|
|
||||||
| `camera` | bool \| `auto` | `auto` | `v4l-utils` for UVC webcams. `auto` = install when a UVC/IPU6 camera is detected (IPU6 out-of-tree stack is logged, not auto-installed) |
|
|
||||||
| `audio` | bool \| `auto` | `auto` | SOF firmware + ALSA UCM. `auto` = install when an audio device is detected |
|
|
||||||
| `bluetooth` | bool \| `auto` | `auto` | `bluez`. `auto` = install when a Bluetooth controller is detected |
|
|
||||||
| `displaylink` | bool | `false` | DisplayLink dock support (explicit opt-in; see notes) |
|
|
||||||
|
|
||||||
Fingerprint detection scans `lsusb` for known reader vendor IDs (Synaptics, Validity, Goodix, Elan, Egis,
|
|
||||||
Broadcom, AuthenTec, Upek, Futronic). When `fingerprint: auto` and a reader is present, `fprintd` and the
|
|
||||||
PAM helper are installed. PAM enrollment must be done post-install (`fprintd-enroll`).
|
|
||||||
|
|
||||||
DisplayLink ships proprietary userspace that distros do not package consistently. The bootstrap installs the
|
|
||||||
in-tree `evdi-dkms` kernel module on Debian/Ubuntu and the `evdi` module on Fedora, but the userspace blob
|
|
||||||
must still be installed manually from DisplayLink's site after first boot. Arch users typically use AUR
|
|
||||||
(`displaylink`); this is not wired into the bootstrap.
|
|
||||||
|
|
||||||
#### 4.2.9 `system.features.hardware`
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| --------- | ---- | ------- | -------------------------------------------------------------------- |
|
|
||||||
| `profile` | dict | `{}` | Full override: non-empty SKIPS detection (golden image); empty = autodetect |
|
|
||||||
| group fields | mixed | -- | `cpu`/`gpus`/`wireless`/`audio`/`camera`/`fingerprint`/`bluetooth`/`packages`/`disable`/`kernel_params` MERGE over autodetect (see below) |
|
|
||||||
|
|
||||||
When empty, hardware is detected at the start of the bootstrap. When set, detection is skipped and the
|
|
||||||
supplied profile drives package selection - this is the **golden-image** flow: bake an image with a fixed
|
|
||||||
profile, snapshot it, and reuse the same profile on every deploy of that hardware class.
|
|
||||||
|
|
||||||
Profile shape:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
features:
|
|
||||||
hardware:
|
|
||||||
profile:
|
|
||||||
cpu: intel # intel | amd
|
|
||||||
gpus: [intel, nvidia] # any of: intel, amd, nvidia
|
|
||||||
nvidia_supports_open: true # set false to force proprietary/nouveau
|
|
||||||
wireless: [intel] # any of: intel, amd, atheros, broadcom,
|
|
||||||
# mediatek, marvell, realtek, qcom, cirrus
|
|
||||||
fingerprint: false # set true to force fprintd install
|
|
||||||
```
|
|
||||||
|
|
||||||
The same keys (minus `profile`) can also be set **directly under `hardware`** as a
|
|
||||||
declarative **hardware group** that MERGES over auto-detection (auto-detect = base; the
|
|
||||||
group supplements/overrides it). Unlike `profile`, which skips detection entirely, the
|
|
||||||
group keeps detection running and layers on top - use it to pin everything a known device
|
|
||||||
needs so nothing is ever under-set.
|
|
||||||
|
|
||||||
| Key | Type | Merge semantics |
|
|
||||||
| ------------------------- | ---- | -------------------------------------------------------- |
|
|
||||||
| `cpu` | str | pin the CPU vendor (overrides detection when non-empty) |
|
|
||||||
| `gpus`/`wireless`/`audio` | list | union with the detected vendor codes |
|
|
||||||
| `camera` | dict | `{uvc, ipu6}` booleans OR'd with detection |
|
|
||||||
| `fingerprint`/`bluetooth` | bool | OR'd with detection (force-on) |
|
|
||||||
| `packages` | dict | per-`os_family` extra packages, added to the install set (deduped; empty entries dropped) |
|
|
||||||
| `disable` | list | feature/vendor names force-off, applied last |
|
|
||||||
| `kernel_params` | list | extra kernel cmdline params, appended to the bootloader |
|
|
||||||
|
|
||||||
Example - a laptop with an Intel IPU6 camera (out-of-tree stack) and a Cirrus amp, pinned
|
|
||||||
in a group's `group_vars`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
features:
|
|
||||||
hardware:
|
|
||||||
bluetooth: true # force-on if detection misses the combo card
|
|
||||||
camera:
|
|
||||||
ipu6: true # force the IPU6 path
|
|
||||||
packages: # out-of-tree/AUR bits detection must not auto-install
|
|
||||||
Archlinux: [intel-ipu6-dkms, v4l2-relayd, linux-firmware-cirrus]
|
|
||||||
disable: [displaylink] # never pull DisplayLink on this device
|
|
||||||
kernel_params: ["i915.enable_psr=0"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 `hypervisor` Dictionary
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| ------------ | ------ | ------- | ---------------------------------------------------- |
|
|
||||||
| `type` | string | -- | `libvirt`, `proxmox`, `vmware`, `xen`, or `none` |
|
|
||||||
| `url` | string | -- | API host (Proxmox/VMware) |
|
|
||||||
| `username` | string | -- | API username |
|
|
||||||
| `password` | string | -- | API password |
|
|
||||||
| `node` | string | -- | Target compute node (Proxmox node / VMware ESXi host; mutually exclusive with `cluster` on VMware) |
|
|
||||||
| `storage` | string | -- | Storage identifier (Proxmox/VMware) |
|
|
||||||
| `datacenter` | string | -- | VMware datacenter |
|
|
||||||
| `cluster` | string | -- | VMware cluster |
|
|
||||||
| `certs` | bool | `false` | TLS certificate validation (VMware) |
|
|
||||||
| `ssh` | bool | `false` | Enable SSH on guest and switch connection (VMware) |
|
|
||||||
|
|
||||||
### 4.4 CIS Hardening
|
|
||||||
|
|
||||||
When `system.features.cis.enabled: true`, the CIS role applies hardening. The behaviour is driven by three keys under `system.features.cis`:
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
| --------- | ------ | ----------- | ----------------------------------------------------------------- |
|
|
||||||
| `enabled` | bool | `false` | Apply CIS hardening at all |
|
|
||||||
| `profile` | string | `default` | `default` (house baseline), `l1` (clean CIS Level 1), or `l2` |
|
|
||||||
| `rules` | dict | `{}` | Per-rule on/off overrides on top of the profile |
|
|
||||||
| `params` | dict | `{}` | Parameter overrides (deep-merged; list values replace wholesale) |
|
|
||||||
|
|
||||||
**Profiles.** `default` is the established house baseline (CIS Level 1 plus the USB lockdown, full module blacklist, and IPv6-disable extras, minus the usability-hostile controls). `l1` is a clean CIS Level 1: it drops the L2 extras and adds password aging, AIDE, and warning banners. `l2` is `l1` plus auditd and the L2 extras.
|
|
||||||
|
|
||||||
**Per-rule overrides.** Toggle an individual rule without changing profile, e.g. keep the default profile but allow USB and IPv6 on a desktop:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
features:
|
|
||||||
cis:
|
|
||||||
enabled: true
|
|
||||||
rules:
|
|
||||||
usb_lockdown: false
|
|
||||||
ipv6_disable: false
|
|
||||||
```
|
|
||||||
|
|
||||||
Rule keys: `module_blacklist`, `usb_lockdown`, `sysctl_hardening`, `ipv6_disable`, `umask_default`, `empty_password_login`, `pwquality`, `core_dumps`, `shell_timeout`, `journald_persistent`, `sudo_logfile`, `su_restriction`, `faillock`, `password_history`, `tcp_wrappers`, `crypto_policy`, `mask_services`, `cron_at_access`, `file_permissions`, `sshd_hardening`, `password_expiry`, `aide`, `warning_banners`, `auditd`, and the opt-in `grub_password` (set `rules.grub_password: true` with `params.grub_password_hash`).
|
|
||||||
|
|
||||||
**Parameters.** Override baseline values under `params` (full list in `roles/cis/vars/main.yml`):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
features:
|
|
||||||
cis:
|
|
||||||
enabled: true
|
|
||||||
profile: l1
|
|
||||||
params:
|
|
||||||
pwquality_minlen: 16
|
|
||||||
sysctl: # dict: deep-merged over the profile's set
|
|
||||||
net.ipv4.ip_forward: 1
|
|
||||||
sshd_options: # list: REPLACES the entire default list
|
|
||||||
- {option: X11Forwarding, value: "yes"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Common params: `modules_blacklist` (list), `sysctl` (dict), `sshd_options` (list), `pwquality_minlen` (14), `tmout` (900), `umask` (077), `umask_profile` (027), `faillock_deny` (5), `faillock_unlock_time` (900), `password_remember` (5), `pass_max_days` (365), `aide_cron_hour`/`aide_cron_minute`, `banner_text`, `grub_password_hash`.
|
|
||||||
|
|
||||||
### 4.5 VMware Guest Operations
|
|
||||||
|
|
||||||
When `hypervisor.type: vmware` uses the `vmware_tools` connection:
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
| ------------------------------- | -------------------------------------------- |
|
|
||||||
| `ansible_vmware_tools_user` | Guest OS username |
|
|
||||||
| `ansible_vmware_tools_password` | Guest OS password |
|
|
||||||
| `ansible_vmware_guest_path` | VM inventory path |
|
|
||||||
| `ansible_vmware_host` | vCenter/ESXi hostname |
|
|
||||||
| `ansible_vmware_user` | vCenter/ESXi API username |
|
|
||||||
| `ansible_vmware_password` | vCenter/ESXi API password |
|
|
||||||
| `ansible_vmware_validate_certs` | TLS certificate validation |
|
|
||||||
|
|
||||||
### 4.6 Multi-Disk Schema
|
|
||||||
|
|
||||||
`system.disks[0]` is the OS disk (no `mount.path`). Additional entries define data disks.
|
|
||||||
|
|
||||||
| Key | Type | Description |
|
|
||||||
| ------------- | ------ | ------------------------------------------------------ |
|
|
||||||
| `size` | number | Disk size in GB (required for virtual) |
|
|
||||||
| `device` | string | Block device path (required for physical data disks) |
|
|
||||||
| `partition` | string | Derived from `device` during normalization (not user input) |
|
|
||||||
| `mount.path` | string | Mount point (additional disks only) |
|
|
||||||
| `mount.fstype`| string | `btrfs`, `ext4`, or `xfs` |
|
|
||||||
| `mount.label` | string | Filesystem label |
|
|
||||||
| `mount.opts` | string | Mount options (default: `defaults`) |
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
system:
|
|
||||||
disks:
|
|
||||||
- size: 80 # OS disk
|
|
||||||
- size: 200 # Data disk
|
|
||||||
mount:
|
|
||||||
path: /data
|
|
||||||
fstype: xfs
|
|
||||||
label: DATA
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.7 Advanced Partitioning Overrides
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
| ------------------------------ | ------------ | ---------------------------------------- |
|
|
||||||
| `partitioning_efi_size_mib` | `512` | EFI system partition size in MiB |
|
|
||||||
| `partitioning_boot_size_mib` | `1024` | Separate `/boot` size in MiB |
|
|
||||||
| `partitioning_separate_boot` | auto-derived | Force a separate `/boot` partition |
|
|
||||||
| `partitioning_boot_fs_fstype` | auto-derived | Filesystem for `/boot` |
|
|
||||||
| `partitioning_use_full_disk` | `true` | Use remaining VG space for root LV |
|
|
||||||
|
|
||||||
**Swap sizing:** RAM >= 16GB gets swap = RAM/2. RAM < 16GB gets swap = max(RAM_GB, 2GB). Further capped to prevent over-allocation on small disks.
|
|
||||||
|
|
||||||
**LVM layout** (when not using btrfs): root, swap, and when CIS is enabled: `/home` (2-20GB, 10% of disk), `/var` (2GB), `/var/log` (2GB), `/var/log/audit` (1.5GB).
|
|
||||||
|
|
||||||
### 4.8 Cleanup Defaults
|
|
||||||
|
|
||||||
Post-install verification and recovery settings.
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
| --------------------------- | ------- | ----------------------------------------------------- |
|
|
||||||
| `cleanup_verify_boot` | `true` | Check VM accessibility after reboot |
|
|
||||||
| `cleanup_boot_timeout` | `300` | Timeout in seconds for boot verification |
|
|
||||||
| `cleanup_remove_on_failure` | `true` | Auto-remove VMs that fail to boot (created this run only) |
|
|
||||||
|
|
||||||
## 5. Execution Pipeline
|
|
||||||
|
|
||||||
Roles execute in this order:
|
|
||||||
|
|
||||||
1. **global_defaults** -- normalize inputs, validate, set OS flags
|
|
||||||
2. **system_check** -- detect installer environment, verify live/non-prod target
|
|
||||||
3. **virtualization** -- create VM (if virtual), attach disks, cloud-init
|
|
||||||
4. **environment** -- prepare installer: mount ISO, configure repos, setup pacman, detect hardware
|
|
||||||
5. **partitioning** -- create partitions, LVM, LUKS, mount filesystems
|
|
||||||
6. **bootstrap** -- install base system, packages, and vendor-matched hardware bits
|
|
||||||
7. **configuration** -- users, fstab, locales, bootloader, encryption enrollment, networking
|
|
||||||
8. **cis** -- CIS hardening (when `system.features.cis.enabled: true`)
|
|
||||||
9. **cleanup** -- unmount, shutdown installer, remove media, verify boot
|
|
||||||
|
|
||||||
## 6. Usage
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook -i inventory.yml main.yml
|
ansible-playbook -i inventory.yml -e @vars_example.yml main.yml
|
||||||
ansible-playbook -i inventory.yml main.yml -e @vars.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All credentials (`system.users`, `system.root.password`) must be defined in inventory or passed via `-e`.
|
This command prompts Ansible to execute the `main.yml` playbook, applying configurations defined in both the vars file and the inventory file.
|
||||||
|
|
||||||
Example inventory files are included:
|
Use `inventory_example.yml`, `inventory_libvirt_example.yml`, `vars_example.yml`, and the bare-metal examples as starting points for new inventories.
|
||||||
|
|
||||||
- `inventory_example.yml` -- Proxmox virtual setup
|
## Notes
|
||||||
- `inventory_libvirt_example.yml` -- libvirt virtual setup
|
|
||||||
- `inventory_baremetal_example.yml` -- bare-metal physical setup
|
|
||||||
|
|
||||||
## 7. Security
|
- `vm_size`/`vm_memory`/`vm_cpus` are required for virtual installs only, physical installs use the full disk.
|
||||||
|
- `vm_dns` and `vm_dns_search` accept comma-separated strings or YAML lists.
|
||||||
Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, user passwords in `system.users`, `system.root.password`).
|
- `hypervisor` determines which backend-specific roles run.
|
||||||
|
- Guest tools are installed based on `hypervisor`: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`, otherwise none.
|
||||||
## 8. Safety
|
- Molecule is scaffolded with a delegated driver and a no-op converge for lint-only validation.
|
||||||
|
- With LUKS enabled on Debian/Ubuntu and RHEL-based systems, provisioning uses an ESP (512 MiB), a separate `/boot`
|
||||||
The playbook aborts on non-live/production targets. It refuses to touch pre-existing VMs and only cleans up VMs created in the current run.
|
(1 GiB, same as `filesystem` unless `btrfs` uses ext4 on Debian/Ubuntu or xfs on RHEL-based), and the encrypted root;
|
||||||
|
adjust sizes via
|
||||||
|
`partitioning_efi_size_mib` and `partitioning_boot_size_mib` if needed.
|
||||||
|
- With `luks_auto_decrypt_method: tpm2` on virtual installs, the virtualization role enables a TPM2 device (libvirt/proxmox/vmware).
|
||||||
|
- For VMware, `vmware_ssh: true` enables SSH on the guest and switches the connection to SSH for the remaining tasks.
|
||||||
|
- For physical installs, set `ansible_user`/`ansible_password` for the installer environment when it differs from the prompted user credentials.
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[defaults]
|
|
||||||
hash_behaviour = merge
|
|
||||||
interpreter_python = auto_silent
|
|
||||||
deprecation_warnings = False
|
|
||||||
host_key_checking = False
|
|
||||||
|
|
||||||
[ssh_connection]
|
|
||||||
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=30 -o ServerAliveCountMax=10
|
|
||||||
@@ -1,16 +1,9 @@
|
|||||||
---
|
---
|
||||||
collections:
|
collections:
|
||||||
- name: ansible.posix
|
- name: ansible.posix
|
||||||
version: "2.1.0"
|
|
||||||
- name: community.general
|
- name: community.general
|
||||||
version: "12.3.0"
|
|
||||||
- name: community.libvirt
|
- name: community.libvirt
|
||||||
version: "2.0.0"
|
|
||||||
- name: community.crypto
|
- name: community.crypto
|
||||||
version: "3.1.0"
|
|
||||||
- name: community.proxmox
|
- name: community.proxmox
|
||||||
version: "1.5.0"
|
|
||||||
- name: community.vmware
|
- name: community.vmware
|
||||||
version: "6.2.0"
|
|
||||||
- name: vmware.vmware
|
- name: vmware.vmware
|
||||||
version: "2.7.0"
|
|
||||||
|
|||||||
@@ -1,30 +1,9 @@
|
|||||||
---
|
---
|
||||||
all:
|
all:
|
||||||
vars:
|
|
||||||
hypervisor:
|
|
||||||
type: "none"
|
|
||||||
system:
|
|
||||||
filesystem: "ext4"
|
|
||||||
hosts:
|
hosts:
|
||||||
baremetal01.example.com:
|
baremetal01.example.com:
|
||||||
ansible_host: 10.0.0.162
|
ansible_host: 10.0.0.162
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
ansible_password: "CHANGE_ME"
|
ansible_password: "1234"
|
||||||
ansible_become_password: "CHANGE_ME"
|
ansible_become_password: "1234"
|
||||||
# Required for physical installs: confirms the operator accepts that
|
hostname: "baremetal01.example.com"
|
||||||
# install_drive will be wiped. system_check refuses to run without it.
|
|
||||||
physical_install_confirmed: true
|
|
||||||
system:
|
|
||||||
type: "physical"
|
|
||||||
os: "archlinux"
|
|
||||||
name: "baremetal01.example.com"
|
|
||||||
disks:
|
|
||||||
- device: "/dev/sda"
|
|
||||||
size: 120
|
|
||||||
users:
|
|
||||||
admin:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
|
|||||||
@@ -1,133 +1,50 @@
|
|||||||
---
|
---
|
||||||
all:
|
all:
|
||||||
vars:
|
vars:
|
||||||
hypervisor:
|
install_type: "virtual"
|
||||||
type: "proxmox"
|
hypervisor: "proxmox"
|
||||||
url: "pve01.example.com"
|
install_drive: "/dev/sda"
|
||||||
username: "root@pam"
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
node: "pve01"
|
|
||||||
storage: "local-lvm"
|
|
||||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
boot_iso: "local:iso/archlinux-x86_64.iso"
|
||||||
|
vm_nif: "vmbr0"
|
||||||
children:
|
children:
|
||||||
proxmox:
|
proxmox:
|
||||||
hosts:
|
hosts:
|
||||||
app01.example.com:
|
app01.example.com:
|
||||||
ansible_host: 10.0.0.10
|
ansible_host: 10.0.0.10
|
||||||
system:
|
hostname: "app01.example.com"
|
||||||
filesystem: "btrfs"
|
os: "archlinux"
|
||||||
type: "virtual"
|
filesystem: "btrfs"
|
||||||
os: "archlinux"
|
vm_id: 100
|
||||||
name: "app01.example.com"
|
vm_cpus: 2
|
||||||
id: 100
|
vm_memory: 4096
|
||||||
cpus: 2
|
vm_size: 40
|
||||||
memory: 4096
|
vm_ip: 10.0.0.10
|
||||||
balloon: 0
|
vm_nms: 24
|
||||||
network:
|
vm_gw: 10.0.0.1
|
||||||
bridge: "vmbr0"
|
vm_dns:
|
||||||
ip: 10.0.0.10
|
- 1.1.1.1
|
||||||
prefix: 24
|
- 1.0.0.1
|
||||||
gateway: 10.0.0.1
|
extra_packages:
|
||||||
dns:
|
- jq
|
||||||
servers:
|
- tmux
|
||||||
- 1.1.1.1
|
|
||||||
- 1.0.0.1
|
|
||||||
search:
|
|
||||||
- example.com
|
|
||||||
disks:
|
|
||||||
- size: 40
|
|
||||||
- size: 80
|
|
||||||
mount:
|
|
||||||
path: /data
|
|
||||||
fstype: xfs
|
|
||||||
label: DATA
|
|
||||||
opts: defaults
|
|
||||||
users:
|
|
||||||
ops:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
packages:
|
|
||||||
- jq
|
|
||||||
- tmux
|
|
||||||
features:
|
|
||||||
cis:
|
|
||||||
enabled: false
|
|
||||||
selinux:
|
|
||||||
enabled: true
|
|
||||||
firewall:
|
|
||||||
enabled: true
|
|
||||||
backend: "firewalld"
|
|
||||||
toolkit: "nftables"
|
|
||||||
ssh:
|
|
||||||
enabled: true
|
|
||||||
zstd:
|
|
||||||
enabled: true
|
|
||||||
swap:
|
|
||||||
enabled: true
|
|
||||||
banner:
|
|
||||||
motd: true
|
|
||||||
sudo: true
|
|
||||||
chroot:
|
|
||||||
tool: "arch-chroot"
|
|
||||||
db01.example.com:
|
db01.example.com:
|
||||||
ansible_host: 10.0.0.11
|
ansible_host: 10.0.0.11
|
||||||
|
hostname: "db01.example.com"
|
||||||
|
os: "rhel9"
|
||||||
|
filesystem: "xfs"
|
||||||
|
vm_id: 101
|
||||||
|
vm_cpus: 4
|
||||||
|
vm_memory: 8192
|
||||||
|
vm_size: 80
|
||||||
|
vm_ip: 10.0.0.11
|
||||||
|
vm_nms: 24
|
||||||
|
vm_gw: 10.0.0.1
|
||||||
|
vm_dns: "1.1.1.1,1.0.0.1"
|
||||||
rhel_iso: "local:iso/rhel-9.4-x86_64-dvd.iso"
|
rhel_iso: "local:iso/rhel-9.4-x86_64-dvd.iso"
|
||||||
system:
|
luks_enabled: true
|
||||||
filesystem: "xfs"
|
luks_passphrase: "CHANGE_ME"
|
||||||
type: "virtual"
|
luks_auto_decrypt_method: "keyfile"
|
||||||
os: "rhel"
|
luks_keyfile_size: 128
|
||||||
version: "9"
|
cis: true
|
||||||
name: "db01.example.com"
|
selinux: false
|
||||||
id: 101
|
firewalld_enabled: false
|
||||||
cpus: 4
|
|
||||||
memory: 8192
|
|
||||||
network:
|
|
||||||
bridge: "vmbr0"
|
|
||||||
ip: 10.0.0.11
|
|
||||||
prefix: 24
|
|
||||||
gateway: 10.0.0.1
|
|
||||||
dns:
|
|
||||||
servers:
|
|
||||||
- "1.1.1.1"
|
|
||||||
- "1.0.0.1"
|
|
||||||
disks:
|
|
||||||
- size: 80
|
|
||||||
- size: 200
|
|
||||||
mount:
|
|
||||||
path: /srv/data
|
|
||||||
fstype: ext4
|
|
||||||
users:
|
|
||||||
dbadmin:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
luks:
|
|
||||||
enabled: true
|
|
||||||
passphrase: "CHANGE_ME"
|
|
||||||
method: "keyfile"
|
|
||||||
keysize: 128
|
|
||||||
features:
|
|
||||||
cis:
|
|
||||||
enabled: true
|
|
||||||
selinux:
|
|
||||||
enabled: false
|
|
||||||
firewall:
|
|
||||||
enabled: false
|
|
||||||
backend: "firewalld"
|
|
||||||
toolkit: "nftables"
|
|
||||||
ssh:
|
|
||||||
enabled: true
|
|
||||||
zstd:
|
|
||||||
enabled: true
|
|
||||||
swap:
|
|
||||||
enabled: true
|
|
||||||
banner:
|
|
||||||
motd: true
|
|
||||||
sudo: true
|
|
||||||
chroot:
|
|
||||||
tool: "arch-chroot"
|
|
||||||
|
|||||||
@@ -1,133 +1,56 @@
|
|||||||
---
|
---
|
||||||
all:
|
all:
|
||||||
vars:
|
vars:
|
||||||
hypervisor:
|
install_type: "virtual"
|
||||||
type: "libvirt"
|
hypervisor: "libvirt"
|
||||||
url: "localhost"
|
install_drive: "/dev/vda"
|
||||||
username: ""
|
|
||||||
password: ""
|
|
||||||
storage: "default"
|
|
||||||
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
|
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
|
||||||
children:
|
children:
|
||||||
libvirt:
|
libvirt:
|
||||||
hosts:
|
hosts:
|
||||||
web01.local:
|
web01.example.com:
|
||||||
ansible_host: 192.168.122.20
|
ansible_host: 192.168.122.10
|
||||||
system:
|
hostname: "web01.example.com"
|
||||||
filesystem: "ext4"
|
os: "debian12"
|
||||||
type: "virtual"
|
filesystem: "ext4"
|
||||||
os: "debian"
|
vm_cpus: 2
|
||||||
version: "12"
|
vm_memory: 2048
|
||||||
name: "web01.local"
|
vm_size: 30
|
||||||
cpus: 2
|
vm_ip: 192.168.122.10
|
||||||
memory: 2048
|
vm_nms: 24
|
||||||
network:
|
vm_gw: 192.168.122.1
|
||||||
bridge: "default"
|
vm_dns: 1.1.1.1
|
||||||
ip: 192.168.122.20
|
extra_packages:
|
||||||
prefix: 24
|
- nginx
|
||||||
gateway: 192.168.122.1
|
- fail2ban
|
||||||
dns:
|
vault01.example.com:
|
||||||
servers:
|
ansible_host: 192.168.122.11
|
||||||
- 1.1.1.1
|
hostname: "vault01.example.com"
|
||||||
search:
|
os: "ubuntu-lts"
|
||||||
- lab.local
|
filesystem: "btrfs"
|
||||||
path: "/var/lib/libvirt/images"
|
vm_cpus: 2
|
||||||
disks:
|
vm_memory: 4096
|
||||||
- size: 30
|
vm_size: 40
|
||||||
- size: 80
|
vm_ip: 192.168.122.11
|
||||||
mount:
|
vm_nms: 24
|
||||||
path: /var/www
|
vm_gw: 192.168.122.1
|
||||||
fstype: xfs
|
vm_dns_search: "example.com"
|
||||||
users:
|
luks_enabled: true
|
||||||
web:
|
luks_passphrase: "CHANGE_ME"
|
||||||
password: "CHANGE_ME"
|
luks_auto_decrypt_method: "keyfile"
|
||||||
keys:
|
firewalld_enabled: false
|
||||||
- "ssh-ed25519 AAAA..."
|
rhel9.example.com:
|
||||||
root:
|
ansible_host: 192.168.122.12
|
||||||
password: "CHANGE_ME"
|
hostname: "rhel9.example.com"
|
||||||
packages:
|
os: "rhel9"
|
||||||
- nginx
|
filesystem: "xfs"
|
||||||
- curl
|
vm_cpus: 4
|
||||||
features:
|
vm_memory: 8192
|
||||||
firewall:
|
vm_size: 80
|
||||||
enabled: true
|
vm_ip: 192.168.122.12
|
||||||
backend: "ufw"
|
vm_nms: 24
|
||||||
toolkit: "nftables"
|
vm_gw: 192.168.122.1
|
||||||
db01.local:
|
vm_dns: "1.1.1.1,1.0.0.1"
|
||||||
ansible_host: 192.168.122.21
|
vm_path: "/srv/libvirt/images"
|
||||||
rhel_iso: "/var/lib/libvirt/images/rhel-9.4-x86_64-dvd.iso"
|
rhel_iso: "/var/lib/libvirt/images/rhel-9.4-x86_64-dvd.iso"
|
||||||
system:
|
vlan_name: "100"
|
||||||
filesystem: "xfs"
|
|
||||||
type: "virtual"
|
|
||||||
os: "rhel"
|
|
||||||
version: "9"
|
|
||||||
name: "db01.local"
|
|
||||||
cpus: 4
|
|
||||||
memory: 4096
|
|
||||||
network:
|
|
||||||
bridge: "default"
|
|
||||||
ip: 192.168.122.21
|
|
||||||
prefix: 24
|
|
||||||
gateway: 192.168.122.1
|
|
||||||
dns:
|
|
||||||
servers:
|
|
||||||
- 9.9.9.9
|
|
||||||
search:
|
|
||||||
- example.com
|
|
||||||
disks:
|
|
||||||
- size: 60
|
|
||||||
- size: 120
|
|
||||||
mount:
|
|
||||||
path: /data
|
|
||||||
fstype: ext4
|
|
||||||
users:
|
|
||||||
db:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
luks:
|
|
||||||
enabled: true
|
|
||||||
passphrase: "CHANGE_ME"
|
|
||||||
method: "keyfile"
|
|
||||||
features:
|
|
||||||
firewall:
|
|
||||||
enabled: false
|
|
||||||
backend: "firewalld"
|
|
||||||
toolkit: "nftables"
|
|
||||||
compute01.local:
|
|
||||||
ansible_host: 192.168.122.22
|
|
||||||
system:
|
|
||||||
filesystem: "btrfs"
|
|
||||||
type: "virtual"
|
|
||||||
os: "fedora"
|
|
||||||
version: "41"
|
|
||||||
name: "compute01.local"
|
|
||||||
cpus: 8
|
|
||||||
memory: 8192
|
|
||||||
network:
|
|
||||||
bridge: "default"
|
|
||||||
ip: 192.168.122.22
|
|
||||||
prefix: 24
|
|
||||||
gateway: 192.168.122.1
|
|
||||||
dns:
|
|
||||||
servers:
|
|
||||||
- "1.1.1.1"
|
|
||||||
- "1.0.0.1"
|
|
||||||
disks:
|
|
||||||
- size: 80
|
|
||||||
- size: 200
|
|
||||||
mount:
|
|
||||||
path: /data
|
|
||||||
fstype: btrfs
|
|
||||||
users:
|
|
||||||
compute:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
features:
|
|
||||||
cis:
|
|
||||||
enabled: true
|
|
||||||
|
|||||||
201
main.yml
201
main.yml
@@ -1,101 +1,60 @@
|
|||||||
---
|
---
|
||||||
- name: Create and configure VMs
|
- name: Create and configure VMs
|
||||||
hosts: "{{ bootstrap_target | default('all') }}"
|
hosts: all
|
||||||
strategy: free # noqa: run-once[play]
|
strategy: free # noqa: run-once[play]
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
become: true
|
become: true
|
||||||
|
vars_prompt:
|
||||||
|
- name: user_name
|
||||||
|
prompt: |
|
||||||
|
What is your username?
|
||||||
|
private: false
|
||||||
|
|
||||||
|
- name: user_public_key
|
||||||
|
prompt: |
|
||||||
|
What is your ssh key?
|
||||||
|
private: false
|
||||||
|
|
||||||
|
- name: user_password
|
||||||
|
prompt: |
|
||||||
|
What is your password?
|
||||||
|
confirm: true
|
||||||
|
|
||||||
|
- name: root_password
|
||||||
|
prompt: |
|
||||||
|
What is your root password?
|
||||||
|
confirm: true
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
- name: Load global defaults
|
- name: Load global defaults
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: global_defaults
|
name: global_defaults
|
||||||
|
|
||||||
- name: Perform safety checks
|
roles:
|
||||||
ansible.builtin.import_role:
|
- role: virtualization
|
||||||
name: system_check
|
when: install_type == "virtual"
|
||||||
|
become: false
|
||||||
|
vars:
|
||||||
|
ansible_connection: local
|
||||||
|
|
||||||
tasks:
|
- role: environment
|
||||||
- name: Bootstrap pipeline
|
vars:
|
||||||
block:
|
ansible_connection: "{{ 'vmware_tools' if hypervisor == 'vmware' else 'ssh' }}"
|
||||||
- name: Record that no pre-existing VM was found
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_vm_absent_before_bootstrap: true
|
|
||||||
|
|
||||||
- name: Create virtual machine
|
- role: partitioning
|
||||||
when: system_cfg.type == "virtual"
|
vars:
|
||||||
ansible.builtin.include_role:
|
partitioning_boot_partition_suffix: 1
|
||||||
name: virtualization
|
partitioning_main_partition_suffix: 2
|
||||||
public: true
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_become: false
|
|
||||||
|
|
||||||
- name: Configure environment
|
- role: bootstrap
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: environment
|
|
||||||
public: true
|
|
||||||
|
|
||||||
- name: Partition disks
|
- role: configuration
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: partitioning
|
|
||||||
public: true
|
|
||||||
vars:
|
|
||||||
partitioning_boot_partition_suffix: 1
|
|
||||||
partitioning_main_partition_suffix: 2
|
|
||||||
|
|
||||||
- name: Install base system
|
- role: cis
|
||||||
ansible.builtin.include_role:
|
when: cis_enabled
|
||||||
name: bootstrap
|
|
||||||
public: true
|
|
||||||
|
|
||||||
- name: Apply system configuration
|
- role: cleanup
|
||||||
ansible.builtin.include_role:
|
when: install_type in ["virtual", "physical"]
|
||||||
name: configuration
|
become: false
|
||||||
public: true
|
|
||||||
|
|
||||||
# Past this point the OS is installed and configured; a CIS hardening or
|
|
||||||
# cleanup failure must not delete an otherwise-good VM.
|
|
||||||
- name: Mark base system complete
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_bootstrap_base_complete: true
|
|
||||||
|
|
||||||
- name: Apply CIS hardening
|
|
||||||
when: system_cfg.features.cis.enabled | bool
|
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: cis
|
|
||||||
public: true
|
|
||||||
|
|
||||||
- name: Clean up and finalize
|
|
||||||
when: system_cfg.type in ["virtual", "physical"]
|
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: cleanup
|
|
||||||
public: true
|
|
||||||
|
|
||||||
rescue:
|
|
||||||
- name: Decide whether to delete the half-built VM
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_delete_vm_on_rescue: >-
|
|
||||||
{{ _vm_absent_before_bootstrap | default(false) | bool
|
|
||||||
and virtualization_vm_created_in_run | default(false) | bool
|
|
||||||
and system_cfg.type == "virtual"
|
|
||||||
and not (_bootstrap_base_complete | default(false) | bool) }}
|
|
||||||
|
|
||||||
- name: Delete VM on bootstrap failure
|
|
||||||
when: _delete_vm_on_rescue | bool
|
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: virtualization
|
|
||||||
tasks_from: delete
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_become: false
|
|
||||||
tags:
|
|
||||||
- rescue_cleanup
|
|
||||||
|
|
||||||
- name: Fail host after bootstrap rescue
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: >-
|
|
||||||
Bootstrap failed for {{ hostname }}.
|
|
||||||
{{ 'VM was deleted to allow clean retry.' if (_delete_vm_on_rescue | bool)
|
|
||||||
else 'VM kept (base system installed or not created this run).' }}
|
|
||||||
|
|
||||||
post_tasks:
|
post_tasks:
|
||||||
- name: Set post-reboot connection flags
|
- name: Set post-reboot connection flags
|
||||||
@@ -103,73 +62,41 @@
|
|||||||
post_reboot_can_connect: >-
|
post_reboot_can_connect: >-
|
||||||
{{
|
{{
|
||||||
(ansible_connection | default('ssh')) != 'ssh'
|
(ansible_connection | default('ssh')) != 'ssh'
|
||||||
or ((system_cfg.network.ip | default('') | string | length) > 0)
|
or (vm_ip is defined and (vm_ip | string | length) > 0)
|
||||||
or (
|
or (
|
||||||
system_cfg.type == 'physical'
|
install_type == 'physical'
|
||||||
and (ansible_host | default('') | string | length) > 0
|
and (ansible_host | default('') | string | length) > 0
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
changed_when: false
|
||||||
- name: Reset SSH connection before post-reboot tasks
|
|
||||||
when:
|
|
||||||
- post_reboot_can_connect | bool
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Set final SSH credentials for post-reboot tasks
|
- name: Set final SSH credentials for post-reboot tasks
|
||||||
when:
|
when:
|
||||||
- post_reboot_can_connect | bool
|
- post_reboot_can_connect | bool
|
||||||
no_log: true
|
|
||||||
vars:
|
|
||||||
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_connection: ssh
|
ansible_user: "{{ user_name }}"
|
||||||
ansible_host: "{{ system_cfg.network.ip }}"
|
ansible_password: "{{ user_password }}"
|
||||||
ansible_port: 22
|
ansible_become_password: "{{ user_password }}"
|
||||||
ansible_user: "{{ _primary.key }}"
|
|
||||||
ansible_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_become_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
|
||||||
|
|
||||||
- name: Wait for the rebooted host to accept SSH
|
- name: Install post-reboot extra packages
|
||||||
when:
|
|
||||||
- post_reboot_can_connect | bool
|
|
||||||
ansible.builtin.wait_for_connection:
|
|
||||||
delay: 5
|
|
||||||
sleep: 5
|
|
||||||
# 600s: a selinux-enabled first boot relabels the filesystem and reboots once more.
|
|
||||||
timeout: 600
|
|
||||||
|
|
||||||
- name: Re-gather facts for target OS after reboot
|
|
||||||
when:
|
|
||||||
- post_reboot_can_connect | bool
|
|
||||||
ansible.builtin.setup:
|
|
||||||
gather_subset:
|
|
||||||
- "!all"
|
|
||||||
- min
|
|
||||||
- pkg_mgr
|
|
||||||
|
|
||||||
- name: Register with the Satellite content source
|
|
||||||
when:
|
|
||||||
- post_reboot_can_connect | bool
|
|
||||||
- system_cfg.content.source == 'satellite'
|
|
||||||
- system_cfg.os | lower in os_family_rhel
|
|
||||||
ansible.builtin.include_tasks: "{{ playbook_dir }}/roles/configuration/tasks/satellite_register.yml"
|
|
||||||
|
|
||||||
- name: Activate the firewall on the rebooted host
|
|
||||||
when:
|
|
||||||
- post_reboot_can_connect | bool
|
|
||||||
- system_cfg.features.firewall.enabled | bool
|
|
||||||
- system_cfg.features.firewall.backend == 'ufw'
|
|
||||||
ansible.builtin.include_tasks: "{{ playbook_dir }}/roles/configuration/tasks/firewall.yml"
|
|
||||||
vars:
|
vars:
|
||||||
firewall_phase: postreboot
|
post_install_extra_packages: >-
|
||||||
|
{{
|
||||||
- name: Install post-reboot packages
|
(
|
||||||
|
extra_packages
|
||||||
|
if (extra_packages is iterable and extra_packages is not string)
|
||||||
|
else (extra_packages | string).split(',')
|
||||||
|
)
|
||||||
|
| map('trim')
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
when:
|
when:
|
||||||
- post_reboot_can_connect | bool
|
- post_reboot_can_connect | bool
|
||||||
- system_cfg.packages is defined
|
- extra_packages is defined
|
||||||
- system_cfg.packages | length > 0
|
- extra_packages | length > 0
|
||||||
|
- post_install_extra_packages | length > 0
|
||||||
ansible.builtin.package:
|
ansible.builtin.package:
|
||||||
name: "{{ system_cfg.packages }}"
|
name: "{{ post_install_extra_packages }}"
|
||||||
state: present
|
state: present
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
# OS -> task file mapping for bootstrap dispatch.
|
|
||||||
# Each key matches a supported `os` value; value is the task file to include.
|
|
||||||
bootstrap_os_task_map:
|
|
||||||
almalinux: _dnf_family.yml
|
|
||||||
archlinux: archlinux.yml
|
|
||||||
debian: debian.yml
|
|
||||||
fedora: _dnf_family.yml
|
|
||||||
rocky: _dnf_family.yml
|
|
||||||
rhel: rhel.yml
|
|
||||||
ubuntu: ubuntu.yml
|
|
||||||
ubuntu-lts: ubuntu.yml
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Load desktop package definitions
|
|
||||||
ansible.builtin.include_vars:
|
|
||||||
file: desktop.yml
|
|
||||||
|
|
||||||
- name: Resolve desktop packages
|
|
||||||
vars:
|
|
||||||
_de: "{{ system_cfg.features.desktop.environment }}"
|
|
||||||
_family_pkgs: "{{ bootstrap_desktop_packages[os_family] | default({}) }}"
|
|
||||||
_de_config: "{{ _family_pkgs[_de] | default({}) }}"
|
|
||||||
_base: "{{ bootstrap_desktop_base_packages[os_family] | default([]) }}"
|
|
||||||
_dm: "{{ system_cfg.features.desktop.display_manager | default('') }}"
|
|
||||||
_dm_override_pkg: "{{ (bootstrap_dm_override_packages[_dm] | default({}))[os_family] | default('') }}"
|
|
||||||
_requested_groups: "{{ system_cfg.features.desktop.groups | default([]) }}"
|
|
||||||
_group_pkgs: >-
|
|
||||||
{{
|
|
||||||
_requested_groups
|
|
||||||
| select('in', desktop_package_groups)
|
|
||||||
| map('extract', desktop_package_groups)
|
|
||||||
| map(attribute=os_family, default=[])
|
|
||||||
| list
|
|
||||||
| sum(start=[])
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_desktop_groups: "{{ _de_config.groups | default([]) }}"
|
|
||||||
_desktop_packages: >-
|
|
||||||
{{
|
|
||||||
((_de_config.packages | default([])) + _base + _group_pkgs + [_dm_override_pkg])
|
|
||||||
| reject('equalto', '')
|
|
||||||
| unique
|
|
||||||
| list
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Validate desktop environment is supported
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- system_cfg.features.desktop.environment in (bootstrap_desktop_packages[os_family] | default({}))
|
|
||||||
fail_msg: >-
|
|
||||||
Desktop environment '{{ system_cfg.features.desktop.environment }}'
|
|
||||||
is not defined for os_family '{{ os_family }}'.
|
|
||||||
Supported: {{ (bootstrap_desktop_packages[os_family] | default({})).keys() | join(', ') }}
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Install desktop package groups
|
|
||||||
when: _desktop_groups | length > 0
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} dnf --releasever={{ os_version_major }}
|
|
||||||
--setopt=install_weak_deps=False group install -y {{ _desktop_groups | join(' ') }}
|
|
||||||
register: _desktop_group_result
|
|
||||||
changed_when: _desktop_group_result.rc == 0
|
|
||||||
|
|
||||||
- name: Install desktop packages
|
|
||||||
when: _desktop_packages | length > 0
|
|
||||||
vars:
|
|
||||||
_install_commands:
|
|
||||||
RedHat: >-
|
|
||||||
{{ chroot_command }} dnf --releasever={{ os_version_major }}
|
|
||||||
--setopt=install_weak_deps=False install -y {{ _desktop_packages | join(' ') }}
|
|
||||||
Debian: >-
|
|
||||||
{{ chroot_command }} env DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt install -y --install-recommends {{ _desktop_packages | join(' ') }}
|
|
||||||
Archlinux: >-
|
|
||||||
pacstrap /mnt {{ _desktop_packages | join(' ') }}
|
|
||||||
ansible.builtin.command: "{{ _install_commands[os_family] }}"
|
|
||||||
register: _desktop_pkg_result
|
|
||||||
changed_when: _desktop_pkg_result.rc == 0
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
---
|
|
||||||
- name: "Bootstrap {{ os | capitalize }}"
|
|
||||||
vars:
|
|
||||||
_dnf_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
|
||||||
_dnf_repos: "{{ _dnf_config.repos | map('regex_replace', '^', '--repo=') | join(' ') }}"
|
|
||||||
_dnf_groups: "{{ _dnf_config.base | join(' ') }}"
|
|
||||||
_dnf_extra: >-
|
|
||||||
{{
|
|
||||||
((_dnf_config.extra | default([])) + (_dnf_config.conditional | default([])))
|
|
||||||
| reject('equalto', '')
|
|
||||||
| join(' ')
|
|
||||||
}}
|
|
||||||
block:
|
|
||||||
- name: "Install base system for {{ os | capitalize }}"
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
dnf --releasever={{ os_version_major }} --best {{ _dnf_repos }}
|
|
||||||
--installroot=/mnt --setopt=install_weak_deps=False
|
|
||||||
groupinstall -y {{ _dnf_groups }}
|
|
||||||
register: bootstrap_dnf_base_result
|
|
||||||
changed_when: bootstrap_dnf_base_result.rc == 0
|
|
||||||
failed_when:
|
|
||||||
- bootstrap_dnf_base_result.rc != 0
|
|
||||||
- "'scriptlet' not in bootstrap_dnf_base_result.stderr"
|
|
||||||
|
|
||||||
- name: Ensure chroot has DNS resolution
|
|
||||||
ansible.builtin.file:
|
|
||||||
src: /run/NetworkManager/resolv.conf
|
|
||||||
dest: /mnt/etc/resolv.conf
|
|
||||||
state: link
|
|
||||||
force: true
|
|
||||||
|
|
||||||
- name: Install extra packages
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} dnf --releasever={{ os_version_major }} --setopt=install_weak_deps=False
|
|
||||||
install -y {{ _dnf_extra }}
|
|
||||||
register: bootstrap_dnf_extra_result
|
|
||||||
changed_when: bootstrap_dnf_extra_result.rc == 0
|
|
||||||
|
|
||||||
- name: Detect installed kernel package name
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} rpm -q kernel-core"
|
|
||||||
register: bootstrap_dnf_kernel_check
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Reinstall kernel package
|
|
||||||
vars:
|
|
||||||
_kernel_pkg: "{{ 'kernel-core' if bootstrap_dnf_kernel_check.rc == 0 else 'kernel' }}"
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y {{ _kernel_pkg }}"
|
|
||||||
register: bootstrap_dnf_kernel_result
|
|
||||||
changed_when: bootstrap_dnf_kernel_result.rc == 0
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Load hardware package definitions
|
|
||||||
ansible.builtin.include_vars:
|
|
||||||
file: hardware.yml
|
|
||||||
|
|
||||||
- name: Validate hardware support for current os_family
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- os_family in bootstrap_hardware_packages
|
|
||||||
- hardware_profile_active is defined
|
|
||||||
fail_msg: >-
|
|
||||||
Hardware feature requested but no package map for os_family
|
|
||||||
'{{ os_family }}'. Extend roles/bootstrap/vars/hardware.yml.
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
# nvidia_driver: auto -> open (Turing+) -> proprietary (older, if family ships it)
|
|
||||||
# -> nouveau (fallback). Explicit value falls back to nouveau when
|
|
||||||
# the family lacks packages for it.
|
|
||||||
- name: Resolve Nvidia driver flavor
|
|
||||||
vars:
|
|
||||||
_family: "{{ bootstrap_hardware_packages[os_family] }}"
|
|
||||||
_user_driver: "{{ system_cfg.features.gpu.nvidia_driver | default('auto') }}"
|
|
||||||
_has_nvidia: "{{ 'nvidia' in (hardware_profile_active.gpus | default([]) | difference(_hardware_profile_disable | default([]))) }}"
|
|
||||||
_supports_open: "{{ hardware_profile_active.nvidia_supports_open | default(true) | bool }}"
|
|
||||||
_open_pkgs: "{{ _family.gpu_nvidia.open | default([]) }}"
|
|
||||||
_prop_pkgs: "{{ _family.gpu_nvidia.proprietary | default([]) }}"
|
|
||||||
_auto_choice: >-
|
|
||||||
{{
|
|
||||||
('open' if _supports_open and _open_pkgs | length > 0
|
|
||||||
else ('proprietary' if _prop_pkgs | length > 0
|
|
||||||
else 'nouveau'))
|
|
||||||
}}
|
|
||||||
_user_choice: >-
|
|
||||||
{{
|
|
||||||
_auto_choice if _user_driver == 'auto'
|
|
||||||
else (_user_driver
|
|
||||||
if (_family.gpu_nvidia[_user_driver] | default([]) | length > 0)
|
|
||||||
else 'nouveau')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_nvidia_driver_resolved: "{{ _user_choice if _has_nvidia else 'nouveau' }}"
|
|
||||||
|
|
||||||
# Fedora's akmod-nvidia* packages live in RPMFusion non-free, which is not
|
|
||||||
# enabled out of the box; install the release RPM before the package step.
|
|
||||||
- name: Enable RPMFusion non-free for Fedora Nvidia install
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- os == 'fedora'
|
|
||||||
- system_cfg.features.gpu.enabled | bool
|
|
||||||
- _nvidia_driver_resolved in ['open', 'proprietary']
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} dnf install -y
|
|
||||||
https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-{{ os_version_major }}.noarch.rpm
|
|
||||||
https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{{ os_version_major }}.noarch.rpm
|
|
||||||
register: _rpmfusion_result
|
|
||||||
changed_when: _rpmfusion_result.rc == 0
|
|
||||||
|
|
||||||
- name: Resolve hardware package set
|
|
||||||
ansible.builtin.include_tasks: _resolve_hardware_packages.yml
|
|
||||||
|
|
||||||
- name: Report hardware package selection
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
Hardware install ({{ os_family }}):
|
|
||||||
cpu={{ hardware_profile_active.cpu | default('-') }},
|
|
||||||
gpus={{ hardware_profile_active.gpus | default([]) | join(',') | default('-', true) }},
|
|
||||||
nvidia_driver={{ _nvidia_driver_resolved }},
|
|
||||||
wireless={{ hardware_profile_active.wireless | default([]) | join(',') | default('-', true) }},
|
|
||||||
fingerprint={{ hardware_profile_active.fingerprint | default(false) }}
|
|
||||||
-> {{ _hardware_packages | length }} package(s)
|
|
||||||
|
|
||||||
- name: Note Intel IPU6 camera (out-of-tree stack)
|
|
||||||
when: hardware_profile_active.camera.ipu6 | default(false) | bool
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
Intel IPU6 MIPI camera detected. Its driver stack (intel-ipu6 firmware,
|
|
||||||
DKMS module, v4l2-relayd, libcamera) is out-of-tree/AUR and is NOT auto-
|
|
||||||
installed. Pin the packages in a hardware group via
|
|
||||||
system.features.hardware.packages[{{ os_family }}].
|
|
||||||
|
|
||||||
- name: Install hardware packages
|
|
||||||
when: _hardware_packages | length > 0
|
|
||||||
vars:
|
|
||||||
_install_commands:
|
|
||||||
RedHat: >-
|
|
||||||
{{ chroot_command }} dnf --releasever={{ os_version_major }}
|
|
||||||
--setopt=install_weak_deps=False install -y {{ _hardware_packages | join(' ') }}
|
|
||||||
Debian: >-
|
|
||||||
{{ chroot_command }} apt install -y {{ _hardware_packages | join(' ') }}
|
|
||||||
Archlinux: >-
|
|
||||||
pacstrap /mnt {{ _hardware_packages | join(' ') }}
|
|
||||||
ansible.builtin.command: "{{ _install_commands[os_family] }}"
|
|
||||||
register: _hardware_install_result
|
|
||||||
changed_when: _hardware_install_result.rc == 0
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
---
|
|
||||||
# Split out of _hardware.yml so fixtures can seed the inputs and assert the
|
|
||||||
# resolved _hardware_packages list with no chroot/install.
|
|
||||||
- name: Resolve hardware package set
|
|
||||||
vars:
|
|
||||||
_family: "{{ bootstrap_hardware_packages[os_family] }}"
|
|
||||||
_disable: "{{ _hardware_profile_disable | default([]) | list }}"
|
|
||||||
_profile_packages: "{{ (_hardware_profile_packages | default({}))[os_family] | default([]) | list }}"
|
|
||||||
_cpu: "{{ hardware_profile_active.cpu | default('') | string }}"
|
|
||||||
_gpus: "{{ hardware_profile_active.gpus | default([]) | difference(_disable) | list }}"
|
|
||||||
_wifi: "{{ hardware_profile_active.wireless | default([]) | difference(_disable) | list }}"
|
|
||||||
_fp_detected: "{{ hardware_profile_active.fingerprint | default(false) | bool }}"
|
|
||||||
_audio: "{{ hardware_profile_active.audio | default([]) | difference(_disable) | list }}"
|
|
||||||
_bt_detected: "{{ hardware_profile_active.bluetooth | default(false) | bool }}"
|
|
||||||
_firmware_on: "{{ system_cfg.features.firmware.enabled | bool }}"
|
|
||||||
_microcode_on: "{{ _firmware_on and (system_cfg.features.firmware.microcode | bool) }}"
|
|
||||||
_gpu_on: "{{ system_cfg.features.gpu.enabled | bool }}"
|
|
||||||
_peripherals_on: "{{ system_cfg.features.peripherals.enabled | bool }}"
|
|
||||||
_camera_pref: "{{ system_cfg.features.peripherals.camera | default('auto') }}"
|
|
||||||
_camera_uvc: "{{ hardware_profile_active.camera.uvc | default(false) | bool }}"
|
|
||||||
_camera_ipu6: "{{ hardware_profile_active.camera.ipu6 | default(false) | bool }}"
|
|
||||||
_fp_pref: "{{ system_cfg.features.peripherals.fingerprint | default('auto') }}"
|
|
||||||
_audio_pref: "{{ system_cfg.features.peripherals.audio | default('auto') }}"
|
|
||||||
_bt_pref: "{{ system_cfg.features.peripherals.bluetooth | default('auto') }}"
|
|
||||||
_dl_on: "{{ (system_cfg.features.peripherals.displaylink | bool) and ('displaylink' not in _disable) }}"
|
|
||||||
_camera_on: >-
|
|
||||||
{{
|
|
||||||
_peripherals_on
|
|
||||||
and ('camera' not in _disable)
|
|
||||||
and (_camera_pref == 'true' or (_camera_pref == 'auto' and (_camera_uvc or _camera_ipu6)))
|
|
||||||
}}
|
|
||||||
_fp_on: >-
|
|
||||||
{{
|
|
||||||
_peripherals_on
|
|
||||||
and ('fingerprint' not in _disable)
|
|
||||||
and (_fp_pref == 'true' or (_fp_pref == 'auto' and _fp_detected))
|
|
||||||
}}
|
|
||||||
_audio_on: >-
|
|
||||||
{{
|
|
||||||
_peripherals_on
|
|
||||||
and ('audio' not in _disable)
|
|
||||||
and (_audio_pref == 'true' or (_audio_pref == 'auto' and (_audio | length > 0)))
|
|
||||||
}}
|
|
||||||
_bt_on: >-
|
|
||||||
{{
|
|
||||||
_peripherals_on
|
|
||||||
and ('bluetooth' not in _disable)
|
|
||||||
and (_bt_pref == 'true' or (_bt_pref == 'auto' and _bt_detected))
|
|
||||||
}}
|
|
||||||
# Union of GPU/wireless/CPU vendors; CPU vendor is included so Intel-CPU
|
|
||||||
# systems pull i915/iwlwifi firmware via the same vendor split.
|
|
||||||
_cpu_vendor_list: "{{ ([_cpu] if (_cpu | length > 0 and _cpu not in _disable) else []) | list }}"
|
|
||||||
_firmware_vendors: >-
|
|
||||||
{{
|
|
||||||
(_firmware_on | ternary(
|
|
||||||
(_gpus + _wifi + _cpu_vendor_list)
|
|
||||||
| reject('equalto', '') | unique | list,
|
|
||||||
[]
|
|
||||||
))
|
|
||||||
}}
|
|
||||||
_microcode_pkgs: >-
|
|
||||||
{{
|
|
||||||
((_microcode_on and _cpu | length > 0 and _cpu not in _disable) | ternary(
|
|
||||||
_family.cpu_microcode[_cpu] | default([]),
|
|
||||||
[]
|
|
||||||
)) | list
|
|
||||||
}}
|
|
||||||
_firmware_pkgs: >-
|
|
||||||
{{
|
|
||||||
(_firmware_on | ternary(
|
|
||||||
(_family.firmware_base | default([]) | list)
|
|
||||||
+ (_firmware_vendors
|
|
||||||
| map('extract', _family.firmware | default({}))
|
|
||||||
| select('truthy')
|
|
||||||
| list
|
|
||||||
| sum(start=[])),
|
|
||||||
[]
|
|
||||||
)) | list
|
|
||||||
}}
|
|
||||||
_gpu_base_pkgs: "{{ (_gpu_on | ternary(_family.gpu_base | default([]), [])) | list }}"
|
|
||||||
_gpu_vendor_pkgs: >-
|
|
||||||
{{
|
|
||||||
(_gpu_on | ternary(
|
|
||||||
(_gpus | reject('equalto', 'nvidia') | list)
|
|
||||||
| map('extract', _family.gpu | default({}))
|
|
||||||
| select('truthy')
|
|
||||||
| list
|
|
||||||
| sum(start=[]),
|
|
||||||
[]
|
|
||||||
)) | list
|
|
||||||
}}
|
|
||||||
_gpu_nvidia_pkgs: >-
|
|
||||||
{{
|
|
||||||
((_gpu_on and ('nvidia' in _gpus)) | ternary(
|
|
||||||
_family.gpu_nvidia[_nvidia_driver_resolved] | default([]),
|
|
||||||
[]
|
|
||||||
)) | list
|
|
||||||
}}
|
|
||||||
_camera_base_pkgs: >-
|
|
||||||
{{
|
|
||||||
(_camera_on | ternary(_family.camera_base | default([]), [])) | list
|
|
||||||
}}
|
|
||||||
_peripherals_fingerprint_pkgs: >-
|
|
||||||
{{
|
|
||||||
(_fp_on | ternary(_family.peripherals_fingerprint | default([]), [])) | list
|
|
||||||
}}
|
|
||||||
_peripherals_displaylink_pkgs: >-
|
|
||||||
{{
|
|
||||||
(_dl_on | ternary(_family.peripherals_displaylink | default([]), [])) | list
|
|
||||||
}}
|
|
||||||
_audio_base_pkgs: "{{ (_audio_on | ternary(_family.audio_base | default([]), [])) | list }}"
|
|
||||||
_bluetooth_base_pkgs: "{{ (_bt_on | ternary(_family.bluetooth_base | default([]), [])) | list }}"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_hardware_packages: >-
|
|
||||||
{{
|
|
||||||
(_microcode_pkgs + _firmware_pkgs
|
|
||||||
+ _gpu_base_pkgs + _gpu_vendor_pkgs + _gpu_nvidia_pkgs
|
|
||||||
+ _audio_base_pkgs + _bluetooth_base_pkgs
|
|
||||||
+ _camera_base_pkgs + _peripherals_fingerprint_pkgs
|
|
||||||
+ _peripherals_displaylink_pkgs
|
|
||||||
+ _profile_packages)
|
|
||||||
| reject('equalto', '')
|
|
||||||
| unique
|
|
||||||
| list
|
|
||||||
}}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
# Resolve the OS-specific variable namespace and task file for the bootstrap role.
|
|
||||||
- name: Validate OS is supported for bootstrap
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- os is defined
|
|
||||||
- os in bootstrap_os_task_map
|
|
||||||
fail_msg: >-
|
|
||||||
Unsupported OS '{{ os | default("undefined") }}' for bootstrap.
|
|
||||||
Supported: {{ bootstrap_os_task_map | dict2items | map(attribute='key') | join(', ') }}
|
|
||||||
quiet: true
|
|
||||||
20
roles/bootstrap/tasks/almalinux.yml
Normal file
20
roles/bootstrap/tasks/almalinux.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap AlmaLinux 9
|
||||||
|
vars:
|
||||||
|
bootstrap_alma_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.command: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- >-
|
||||||
|
dnf --releasever=9 --best --repo=alma-baseos --installroot=/mnt
|
||||||
|
--setopt=install_weak_deps=False groupinstall -y base core
|
||||||
|
- ln -sf /run/NetworkManager/resolv.conf /mnt/etc/resolv.conf
|
||||||
|
- >-
|
||||||
|
arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_alma_extra }}
|
||||||
|
register: bootstrap_result
|
||||||
|
changed_when: bootstrap_result.rc == 0
|
||||||
@@ -1,51 +1,11 @@
|
|||||||
---
|
---
|
||||||
- name: Bootstrap ArchLinux
|
- name: Bootstrap ArchLinux
|
||||||
vars:
|
vars:
|
||||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
|
||||||
bootstrap_archlinux_packages: >-
|
bootstrap_archlinux_packages: >-
|
||||||
{{
|
{{
|
||||||
((_config.base | default([])) + (_config.conditional | default([])))
|
lookup('vars', bootstrap_var_key)
|
||||||
| reject('equalto', '')
|
|
||||||
| list
|
|
||||||
}}
|
}}
|
||||||
block:
|
ansible.builtin.command: >-
|
||||||
- name: Notify that mirror mode falls back to the public mirrorlist
|
pacstrap /mnt {{ bootstrap_archlinux_packages | reject('equalto', '') | join(' ') }} --asexplicit
|
||||||
when:
|
register: bootstrap_result
|
||||||
- system_cfg.content.source == 'mirror'
|
changed_when: bootstrap_result.rc == 0
|
||||||
- system_cfg.content.url | length == 0
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
content.source is 'mirror' but content.url is empty: keeping the live
|
|
||||||
ISO public mirrorlist (refreshed by reflector). Set content.url to pin
|
|
||||||
a specific mirror.
|
|
||||||
|
|
||||||
- name: Point pacman at the content mirror
|
|
||||||
when: system_cfg.content.url | length > 0
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/pacman.d/mirrorlist
|
|
||||||
content: "Server = {{ system_cfg.content.url }}/$repo/os/$arch\n"
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Refresh Arch keyring in the live environment
|
|
||||||
ansible.builtin.command: pacman -Sy --noconfirm archlinux-keyring
|
|
||||||
environment:
|
|
||||||
http_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
https_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
register: bootstrap_arch_keyring
|
|
||||||
changed_when: bootstrap_arch_keyring.rc == 0
|
|
||||||
|
|
||||||
- name: Install Arch base system
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
pacstrap /mnt {{ bootstrap_archlinux_packages | join(' ') }}
|
|
||||||
environment:
|
|
||||||
http_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
https_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
register: bootstrap_result
|
|
||||||
changed_when: bootstrap_result.rc == 0
|
|
||||||
|
|
||||||
- name: Persist the content mirror in the installed system
|
|
||||||
when: system_cfg.content.url | length > 0
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/pacman.d/mirrorlist
|
|
||||||
content: "Server = {{ system_cfg.content.url }}/$repo/os/$arch\n"
|
|
||||||
mode: "0644"
|
|
||||||
|
|||||||
@@ -3,92 +3,27 @@
|
|||||||
vars:
|
vars:
|
||||||
bootstrap_debian_release: >-
|
bootstrap_debian_release: >-
|
||||||
{{
|
{{
|
||||||
'bookworm' if (os_version | string) == '12'
|
'bullseye' if bootstrap_os_key == 'debian11'
|
||||||
else 'trixie' if (os_version | string) == '13'
|
else 'bookworm' if bootstrap_os_key == 'debian12'
|
||||||
else 'sid' if (os_version | string) == 'unstable'
|
|
||||||
else 'trixie'
|
else 'trixie'
|
||||||
}}
|
}}
|
||||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
bootstrap_debian_base_list: "{{ lookup('vars', bootstrap_var_key).base | default([]) }}"
|
||||||
bootstrap_debian_base_csv: "{{ (['ca-certificates'] + _config.base) | unique | join(',') }}"
|
bootstrap_debian_extra_list: "{{ lookup('vars', bootstrap_var_key).extra | default([]) }}"
|
||||||
bootstrap_debian_extra_args: >-
|
bootstrap_debian_base: "{{ bootstrap_debian_base_list | reject('equalto', '') | join(',') }}"
|
||||||
|
bootstrap_debian_extra: >-
|
||||||
{{
|
{{
|
||||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
(
|
||||||
|
bootstrap_debian_extra_list
|
||||||
|
)
|
||||||
| reject('equalto', '')
|
| reject('equalto', '')
|
||||||
| join(' ')
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
block:
|
ansible.builtin.command: "{{ item }}"
|
||||||
- name: Validate Debian package configuration
|
loop:
|
||||||
ansible.builtin.assert:
|
- >-
|
||||||
that:
|
debootstrap --include={{ bootstrap_debian_base }}
|
||||||
- _config is mapping
|
{{ bootstrap_debian_release }} /mnt http://deb.debian.org/debian/
|
||||||
- _config.base is sequence
|
- "arch-chroot /mnt apt install -y {{ bootstrap_debian_extra }}"
|
||||||
- _config.extra is sequence
|
- arch-chroot /mnt apt remove -y libcups2 libavahi-common3 libavahi-common-data
|
||||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
register: bootstrap_result
|
||||||
quiet: true
|
changed_when: bootstrap_result.rc == 0
|
||||||
|
|
||||||
- name: Check for a debootstrap script for the target release
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/usr/share/debootstrap/scripts/{{ bootstrap_debian_release }}"
|
|
||||||
register: bootstrap_debian_script
|
|
||||||
|
|
||||||
- name: Symlink a missing debootstrap script to the sid base
|
|
||||||
ansible.builtin.file:
|
|
||||||
src: sid
|
|
||||||
dest: "/usr/share/debootstrap/scripts/{{ bootstrap_debian_release }}"
|
|
||||||
state: link
|
|
||||||
when: not bootstrap_debian_script.stat.exists
|
|
||||||
|
|
||||||
- name: Install Debian base system
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
debootstrap --keyring=/usr/share/keyrings/debian-archive-keyring.gpg
|
|
||||||
--include={{ bootstrap_debian_base_csv }}
|
|
||||||
{{ bootstrap_debian_release }} /mnt
|
|
||||||
{{ system_cfg.content.url }}
|
|
||||||
environment:
|
|
||||||
http_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
https_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
register: bootstrap_debian_base_result
|
|
||||||
changed_when: bootstrap_debian_base_result.rc == 0
|
|
||||||
|
|
||||||
- name: Write bootstrap sources.list
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: debian.sources.list.j2
|
|
||||||
dest: /mnt/etc/apt/sources.list
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Configure apt performance tuning
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
|
||||||
content: |
|
|
||||||
Acquire::Retries "3";
|
|
||||||
Acquire::http::Pipeline-Depth "10";
|
|
||||||
APT::Install-Recommends "false";
|
|
||||||
{% if system_cfg.content.proxy | length > 0 %}
|
|
||||||
Acquire::http::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
Acquire::https::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
{% endif %}
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Update package lists
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt update"
|
|
||||||
register: bootstrap_debian_update_result
|
|
||||||
changed_when: bootstrap_debian_update_result.rc == 0
|
|
||||||
|
|
||||||
- name: Upgrade all packages to latest versions
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
|
||||||
register: bootstrap_debian_upgrade_result
|
|
||||||
changed_when: "'0 upgraded' not in bootstrap_debian_upgrade_result.stdout"
|
|
||||||
|
|
||||||
- name: Install extra packages
|
|
||||||
when: bootstrap_debian_extra_args | trim | length > 0
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
|
||||||
register: bootstrap_debian_extra_result
|
|
||||||
changed_when: bootstrap_debian_extra_result.rc == 0
|
|
||||||
|
|
||||||
# Printing (libcups2) and mDNS (libavahi*) are needed by a desktop session,
|
|
||||||
# so keep them when a desktop is requested.
|
|
||||||
- name: Remove unnecessary packages
|
|
||||||
when: not (system_cfg.features.desktop.enabled | bool)
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt remove -y libcups2 libavahi-common3 libavahi-common-data"
|
|
||||||
register: bootstrap_debian_remove_result
|
|
||||||
changed_when: bootstrap_debian_remove_result.rc == 0
|
|
||||||
|
|||||||
22
roles/bootstrap/tasks/fedora.yml
Normal file
22
roles/bootstrap/tasks/fedora.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap Fedora 43
|
||||||
|
vars:
|
||||||
|
bootstrap_fedora_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.command: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- >-
|
||||||
|
dnf --releasever=43 --best --repo=fedora --repo=fedora-updates
|
||||||
|
--installroot=/mnt --setopt=install_weak_deps=False
|
||||||
|
groupinstall -y critical-path-base core
|
||||||
|
- ln -sf /run/NetworkManager/resolv.conf /mnt/etc/resolv.conf
|
||||||
|
- >-
|
||||||
|
arch-chroot /mnt dnf --releasever=43 --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_fedora_extra }}
|
||||||
|
- arch-chroot /mnt dnf reinstall -y kernel-core
|
||||||
|
register: bootstrap_result
|
||||||
|
changed_when: bootstrap_result.rc == 0
|
||||||
@@ -1,77 +1,33 @@
|
|||||||
---
|
---
|
||||||
- name: Validate bootstrap input
|
|
||||||
ansible.builtin.import_tasks: _validate.yml
|
|
||||||
|
|
||||||
- name: Create API filesystem mountpoints in installroot
|
|
||||||
when: os_family == 'RedHat'
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "/mnt/{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
loop:
|
|
||||||
- dev
|
|
||||||
- proc
|
|
||||||
- sys
|
|
||||||
|
|
||||||
- name: Mount API filesystems into installroot
|
|
||||||
when: os_family == 'RedHat'
|
|
||||||
ansible.posix.mount:
|
|
||||||
src: "{{ item.src }}"
|
|
||||||
path: "/mnt/{{ item.path }}"
|
|
||||||
fstype: "{{ item.fstype }}"
|
|
||||||
opts: "{{ item.opts | default(omit) }}"
|
|
||||||
state: ephemeral
|
|
||||||
loop:
|
|
||||||
- { src: proc, path: proc, fstype: proc }
|
|
||||||
- { src: sysfs, path: sys, fstype: sysfs }
|
|
||||||
- { src: /dev, path: dev, fstype: none, opts: bind }
|
|
||||||
- { src: devpts, path: dev/pts, fstype: devpts, opts: "gid=5,mode=620" }
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
|
|
||||||
# Installers write their cache inside the installroot; redirect it off the 2 GiB CIS /var LV.
|
|
||||||
- name: Create bootstrap package-cache directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/.bootstrap-cache
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Redirect package cache off the CIS /var LV
|
|
||||||
ansible.posix.mount:
|
|
||||||
src: /mnt/.bootstrap-cache
|
|
||||||
path: /mnt/var/cache
|
|
||||||
fstype: none
|
|
||||||
opts: bind
|
|
||||||
state: ephemeral
|
|
||||||
|
|
||||||
- name: Run OS-specific bootstrap process
|
- name: Run OS-specific bootstrap process
|
||||||
vars:
|
vars:
|
||||||
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
|
bootstrap_os_key: "{{ os | lower }}"
|
||||||
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
bootstrap_var_key: "{{ 'bootstrap_' + (os | lower | replace('-', '_')) }}"
|
||||||
|
block:
|
||||||
|
- name: Include AlmaLinux bootstrap tasks
|
||||||
|
when: bootstrap_os_key == 'almalinux'
|
||||||
|
ansible.builtin.include_tasks: almalinux.yml
|
||||||
|
|
||||||
# dnf --installroot never runs anaconda, so no authselect profile is selected and
|
- name: Include ArchLinux bootstrap tasks
|
||||||
# /etc/pam.d/system-auth is missing, leaving the system unable to authenticate.
|
when: bootstrap_os_key == 'archlinux'
|
||||||
# local is the right profile: local-auth only, no pam_sss.so, still CIS-capable.
|
ansible.builtin.include_tasks: archlinux.yml
|
||||||
- name: Select default authselect profile for the PAM stack
|
|
||||||
when: is_authselect | bool
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} authselect select local --force"
|
|
||||||
register: bootstrap_authselect_result
|
|
||||||
changed_when: bootstrap_authselect_result.rc == 0
|
|
||||||
|
|
||||||
- name: Install hardware-matched firmware/microcode/GPU/peripheral packages
|
- name: Include Debian bootstrap tasks
|
||||||
when: >-
|
when: bootstrap_os_key in ['debian11', 'debian12', 'debian13']
|
||||||
(system_cfg.features.firmware.enabled | bool)
|
ansible.builtin.include_tasks: debian.yml
|
||||||
or (system_cfg.features.gpu.enabled | bool)
|
|
||||||
or (system_cfg.features.peripherals.enabled | bool)
|
|
||||||
ansible.builtin.include_tasks: _hardware.yml
|
|
||||||
|
|
||||||
- name: Install desktop environment packages
|
- name: Include Fedora bootstrap tasks
|
||||||
when: system_cfg.features.desktop.enabled | bool
|
when: bootstrap_os_key == 'fedora'
|
||||||
ansible.builtin.include_tasks: _desktop.yml
|
ansible.builtin.include_tasks: fedora.yml
|
||||||
|
|
||||||
- name: Ensure chroot uses live environment DNS
|
- name: Include Rocky bootstrap tasks
|
||||||
ansible.builtin.file:
|
when: bootstrap_os_key == 'rocky'
|
||||||
src: /run/NetworkManager/resolv.conf
|
ansible.builtin.include_tasks: rocky.yml
|
||||||
dest: /mnt/etc/resolv.conf
|
|
||||||
state: link
|
- name: Include RHEL bootstrap tasks
|
||||||
force: true
|
when: bootstrap_os_key in ['rhel8', 'rhel9', 'rhel10']
|
||||||
|
ansible.builtin.include_tasks: rhel.yml
|
||||||
|
|
||||||
|
- name: Include Ubuntu bootstrap tasks
|
||||||
|
when: bootstrap_os_key in ['ubuntu', 'ubuntu-lts']
|
||||||
|
ansible.builtin.include_tasks: ubuntu.yml
|
||||||
|
|||||||
@@ -1,59 +1,66 @@
|
|||||||
---
|
---
|
||||||
- name: Bootstrap RHEL System
|
- name: Bootstrap RHEL System
|
||||||
vars:
|
|
||||||
_rhel_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
|
||||||
_rhel_repos: "{{ _rhel_config.repos | map('regex_replace', '^', '--repo=') | join(' ') }}"
|
|
||||||
_rhel_groups: "{{ _rhel_config.base | join(' ') }}"
|
|
||||||
_rhel_extra: >-
|
|
||||||
{{
|
|
||||||
((_rhel_config.extra | default([])) + (_rhel_config.conditional | default([])))
|
|
||||||
| reject('equalto', '')
|
|
||||||
| join(' ')
|
|
||||||
}}
|
|
||||||
block:
|
block:
|
||||||
- name: Install base packages in chroot environment
|
- name: Install base packages in chroot environment
|
||||||
|
vars:
|
||||||
|
bootstrap_rhel_release: "{{ bootstrap_os_key | replace('rhel', '') }}"
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
dnf --releasever={{ os_version_major }} --best {{ _rhel_repos }}
|
dnf --releasever={{ bootstrap_rhel_release }} --repo={{ bootstrap_os_key }}-baseos
|
||||||
--installroot=/mnt
|
--installroot=/mnt
|
||||||
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
||||||
groupinstall -y {{ _rhel_groups }}
|
groupinstall -y core base standard
|
||||||
register: bootstrap_result
|
register: bootstrap_result
|
||||||
changed_when: bootstrap_result.rc == 0
|
changed_when: bootstrap_result.rc == 0
|
||||||
failed_when:
|
|
||||||
- bootstrap_result.rc != 0
|
- name: Ensure chroot has resolv.conf
|
||||||
- "'grub2-common' not in (bootstrap_result.stderr | default(''))"
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
- name: Ensure chroot RHEL DVD directory exists
|
- name: Ensure chroot RHEL DVD directory exists
|
||||||
when: system_cfg.content.source != 'mirror'
|
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /mnt/usr/local/install/redhat/dvd
|
path: /mnt/usr/local/install/redhat/dvd
|
||||||
state: directory
|
state: directory
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: Bind mount RHEL DVD into chroot
|
- name: Bind mount RHEL DVD into chroot
|
||||||
when: system_cfg.content.source != 'mirror'
|
|
||||||
ansible.posix.mount:
|
ansible.posix.mount:
|
||||||
src: /usr/local/install/redhat/dvd
|
src: /usr/local/install/redhat/dvd
|
||||||
path: /mnt/usr/local/install/redhat/dvd
|
path: /mnt/usr/local/install/redhat/dvd
|
||||||
fstype: none
|
fstype: none
|
||||||
opts: bind
|
opts: bind
|
||||||
state: ephemeral
|
state: mounted
|
||||||
|
|
||||||
- name: Rebuild RPM database inside chroot
|
- name: Rebuild RPM database inside chroot
|
||||||
ansible.builtin.command: "{{ chroot_command }} rpm --rebuilddb"
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- arch-chroot
|
||||||
|
- /mnt
|
||||||
|
- rpm
|
||||||
|
- --rebuilddb
|
||||||
register: bootstrap_rpm_rebuild_result
|
register: bootstrap_rpm_rebuild_result
|
||||||
changed_when: bootstrap_rpm_rebuild_result.rc == 0
|
changed_when: bootstrap_rpm_rebuild_result.rc == 0
|
||||||
|
|
||||||
- name: Copy RHEL repo file into chroot environment
|
- name: Copy RHEL repo file into chroot environment
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: /etc/yum.repos.d/rhel.repo
|
src: /etc/yum.repos.d/{{ bootstrap_os_key }}.repo
|
||||||
dest: /mnt/etc/yum.repos.d/redhat.repo
|
dest: /mnt/etc/yum.repos.d/redhat.repo
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
remote_src: true
|
remote_src: true
|
||||||
|
|
||||||
- name: Install additional packages in chroot
|
- name: Install additional packages in chroot
|
||||||
|
vars:
|
||||||
|
bootstrap_rhel_release: "{{ bootstrap_os_key | replace('rhel', '') }}"
|
||||||
|
bootstrap_rhel_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ chroot_command }} dnf --releasever={{ os_version_major }} --best
|
arch-chroot /mnt dnf --releasever={{ bootstrap_rhel_release }}
|
||||||
--setopt=install_weak_deps=False install -y {{ _rhel_extra }}
|
--setopt=install_weak_deps=False install -y {{ bootstrap_rhel_extra }}
|
||||||
register: bootstrap_result
|
register: bootstrap_result
|
||||||
changed_when: bootstrap_result.rc == 0
|
changed_when: bootstrap_result.rc == 0
|
||||||
|
|||||||
21
roles/bootstrap/tasks/rocky.yml
Normal file
21
roles/bootstrap/tasks/rocky.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap RockyLinux 9
|
||||||
|
vars:
|
||||||
|
bootstrap_rocky_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.command: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- >-
|
||||||
|
dnf --releasever=9 --best --repo=rocky-baseos --installroot=/mnt
|
||||||
|
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
||||||
|
groupinstall -y base core
|
||||||
|
- ln -sf /run/NetworkManager/resolv.conf /mnt/etc/resolv.conf
|
||||||
|
- >-
|
||||||
|
arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_rocky_extra }}
|
||||||
|
register: bootstrap_result
|
||||||
|
changed_when: bootstrap_result.rc == 0
|
||||||
@@ -1,85 +1,27 @@
|
|||||||
---
|
---
|
||||||
- name: Bootstrap Ubuntu System
|
- name: Bootstrap Ubuntu System
|
||||||
vars:
|
vars:
|
||||||
# ubuntu = latest non-LTS, ubuntu-lts = latest LTS
|
bootstrap_ubuntu_release: >-
|
||||||
bootstrap_ubuntu_release_map:
|
{{ 'plucky' if bootstrap_os_key == 'ubuntu' else 'noble' }}
|
||||||
ubuntu: questing
|
bootstrap_ubuntu_base_list: "{{ lookup('vars', bootstrap_var_key).base | default([]) }}"
|
||||||
ubuntu-lts: resolute
|
bootstrap_ubuntu_extra_list: "{{ lookup('vars', bootstrap_var_key).extra | default([]) }}"
|
||||||
bootstrap_ubuntu_release: "{{ bootstrap_ubuntu_release_map[os] | default('resolute') }}"
|
bootstrap_ubuntu_base: "{{ bootstrap_ubuntu_base_list | reject('equalto', '') | join(',') }}"
|
||||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
bootstrap_ubuntu_extra: >-
|
||||||
bootstrap_ubuntu_base_csv: "{{ (['ca-certificates'] + _config.base) | unique | join(',') }}"
|
|
||||||
bootstrap_ubuntu_extra_args: >-
|
|
||||||
{{
|
{{
|
||||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
(
|
||||||
|
bootstrap_ubuntu_extra_list
|
||||||
|
)
|
||||||
| reject('equalto', '')
|
| reject('equalto', '')
|
||||||
| join(' ')
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
block:
|
ansible.builtin.command: "{{ item }}"
|
||||||
- name: Validate Ubuntu package configuration
|
loop:
|
||||||
ansible.builtin.assert:
|
- >-
|
||||||
that:
|
debootstrap --include={{ bootstrap_ubuntu_base }}
|
||||||
- _config is mapping
|
{{ bootstrap_ubuntu_release }} /mnt http://archive.ubuntu.com/ubuntu/
|
||||||
- _config.base is sequence
|
- ln -sf /run/NetworkManager/resolv.conf /mnt/etc/resolv.conf
|
||||||
- _config.extra is sequence
|
- arch-chroot /mnt sed -i '1s|$| universe|' /etc/apt/sources.list
|
||||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
- arch-chroot /mnt apt update
|
||||||
quiet: true
|
- "arch-chroot /mnt apt install -y {{ bootstrap_ubuntu_extra }}"
|
||||||
|
register: bootstrap_result
|
||||||
- name: Check for a debootstrap script for the target release
|
changed_when: bootstrap_result.rc == 0
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/usr/share/debootstrap/scripts/{{ bootstrap_ubuntu_release }}"
|
|
||||||
register: bootstrap_ubuntu_script
|
|
||||||
|
|
||||||
- name: Symlink a missing debootstrap script to the ubuntu base
|
|
||||||
ansible.builtin.file:
|
|
||||||
src: gutsy
|
|
||||||
dest: "/usr/share/debootstrap/scripts/{{ bootstrap_ubuntu_release }}"
|
|
||||||
state: link
|
|
||||||
when: not bootstrap_ubuntu_script.stat.exists
|
|
||||||
|
|
||||||
- name: Install Ubuntu base system
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
debootstrap
|
|
||||||
--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg
|
|
||||||
--include={{ bootstrap_ubuntu_base_csv }}
|
|
||||||
{{ bootstrap_ubuntu_release }} /mnt
|
|
||||||
{{ system_cfg.content.url }}
|
|
||||||
environment:
|
|
||||||
http_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
https_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
register: bootstrap_ubuntu_base_result
|
|
||||||
changed_when: bootstrap_ubuntu_base_result.rc == 0
|
|
||||||
|
|
||||||
- name: Write bootstrap sources.list
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: ubuntu.sources.list.j2
|
|
||||||
dest: /mnt/etc/apt/sources.list
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Configure apt performance tuning
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
|
||||||
content: |
|
|
||||||
Acquire::Retries "3";
|
|
||||||
Acquire::http::Pipeline-Depth "10";
|
|
||||||
APT::Install-Recommends "false";
|
|
||||||
{% if system_cfg.content.proxy | length > 0 %}
|
|
||||||
Acquire::http::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
Acquire::https::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
{% endif %}
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Update package lists
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt update"
|
|
||||||
register: bootstrap_ubuntu_update_result
|
|
||||||
changed_when: bootstrap_ubuntu_update_result.rc == 0
|
|
||||||
|
|
||||||
- name: Upgrade all packages to latest versions
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
|
||||||
register: bootstrap_ubuntu_upgrade_result
|
|
||||||
changed_when: "'0 upgraded' not in bootstrap_ubuntu_upgrade_result.stdout"
|
|
||||||
|
|
||||||
- name: Install extra packages
|
|
||||||
when: bootstrap_ubuntu_extra_args | trim | length > 0
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra_args }}"
|
|
||||||
register: bootstrap_ubuntu_extra_result
|
|
||||||
changed_when: bootstrap_ubuntu_extra_result.rc == 0
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# Managed by Ansible.
|
|
||||||
{% set release = bootstrap_debian_release %}
|
|
||||||
{% set mirror = system_cfg.content.url | default('http://deb.debian.org/debian', true) %}
|
|
||||||
{% set components = 'main contrib non-free non-free-firmware' %}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }} {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }} {{ components }}
|
|
||||||
{% if release != 'sid' %}
|
|
||||||
|
|
||||||
deb https://security.debian.org/debian-security {{ release }}-security {{ components }}
|
|
||||||
deb-src https://security.debian.org/debian-security {{ release }}-security {{ components }}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Managed by Ansible.
|
|
||||||
{% set release = bootstrap_ubuntu_release %}
|
|
||||||
{% set mirror = system_cfg.content.url %}
|
|
||||||
{% set components = 'main restricted universe multiverse' %}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }} {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }} {{ components }}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }}-security {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }}-security {{ components }}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }}-backports {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }}-backports {{ components }}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
---
|
|
||||||
# Wayland only: gnome, kde, sway, hyprland. No X11/xorg-server, no X11-only DEs.
|
|
||||||
|
|
||||||
# plasma-login-manager on Arch/Fedora44+ (Plasma 6.6), else sddm.
|
|
||||||
bootstrap_kde_login_manager: >-
|
|
||||||
{{
|
|
||||||
'plasma-login-manager'
|
|
||||||
if (os == 'archlinux' or (os == 'fedora' and (os_version | int) >= 44))
|
|
||||||
else 'sddm'
|
|
||||||
}}
|
|
||||||
|
|
||||||
# Native DMs ride in each DE's package set; only explicit non-native overrides
|
|
||||||
# need a package here. ly is Arch-only (validation rejects it elsewhere first).
|
|
||||||
bootstrap_dm_override_packages:
|
|
||||||
ly:
|
|
||||||
Archlinux: ly
|
|
||||||
|
|
||||||
# EL = non-fedora RedHat.
|
|
||||||
bootstrap_os_is_el: "{{ os in ['almalinux', 'rocky', 'rhel'] }}"
|
|
||||||
bootstrap_os_is_el10: "{{ bootstrap_os_is_el | bool and (os_version | default('0') | int) >= 10 }}"
|
|
||||||
# EL10 renames (evince->papers, eog->loupe, ppd->tuned-ppd); fira-code + mpv absent on EL.
|
|
||||||
bootstrap_desktop_browser: "{{ 'firefox-esr' if os == 'debian' else 'firefox' }}"
|
|
||||||
bootstrap_desktop_pdf: "{{ 'papers' if bootstrap_os_is_el10 | bool else 'evince' }}"
|
|
||||||
bootstrap_desktop_image: "{{ 'loupe' if bootstrap_os_is_el10 | bool else 'eog' }}"
|
|
||||||
bootstrap_desktop_power: "{{ 'tuned-ppd' if bootstrap_os_is_el10 | bool else 'power-profiles-daemon' }}"
|
|
||||||
bootstrap_desktop_redhat_codefont: "{{ '' if bootstrap_os_is_el | bool else 'fira-code-fonts' }}"
|
|
||||||
bootstrap_desktop_redhat_video: "{{ '' if bootstrap_os_is_el | bool else 'mpv' }}"
|
|
||||||
|
|
||||||
bootstrap_desktop_packages:
|
|
||||||
RedHat:
|
|
||||||
gnome:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- gnome-shell
|
|
||||||
- gnome-control-center
|
|
||||||
- nautilus
|
|
||||||
- gnome-session
|
|
||||||
- gdm
|
|
||||||
kde:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- plasma-desktop
|
|
||||||
- plasma-nm
|
|
||||||
- plasma-pa
|
|
||||||
- plasma-systemmonitor
|
|
||||||
- "{{ bootstrap_kde_login_manager }}"
|
|
||||||
- konsole
|
|
||||||
- dolphin
|
|
||||||
- kate
|
|
||||||
- kscreen
|
|
||||||
- kde-gtk-config
|
|
||||||
- xdg-user-dirs
|
|
||||||
- xdg-desktop-portal-kde
|
|
||||||
- bluez
|
|
||||||
Debian:
|
|
||||||
gnome:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- gnome-core
|
|
||||||
- gdm3
|
|
||||||
- gnome-tweaks
|
|
||||||
- xdg-user-dirs
|
|
||||||
kde:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- plasma-desktop
|
|
||||||
- plasma-nm
|
|
||||||
- plasma-pa
|
|
||||||
- "{{ bootstrap_kde_login_manager }}"
|
|
||||||
- konsole
|
|
||||||
- dolphin
|
|
||||||
- kate
|
|
||||||
- kscreen
|
|
||||||
- xdg-user-dirs
|
|
||||||
- xdg-desktop-portal-kde
|
|
||||||
- bluez
|
|
||||||
Archlinux:
|
|
||||||
gnome:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- gnome
|
|
||||||
- gdm
|
|
||||||
- xdg-user-dirs
|
|
||||||
kde:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- plasma-desktop
|
|
||||||
- plasma-nm
|
|
||||||
- plasma-pa
|
|
||||||
- "{{ bootstrap_kde_login_manager }}"
|
|
||||||
- konsole
|
|
||||||
- dolphin
|
|
||||||
- kate
|
|
||||||
- kscreen
|
|
||||||
- kde-gtk-config
|
|
||||||
- xdg-user-dirs
|
|
||||||
- xdg-desktop-portal-kde
|
|
||||||
- bluez
|
|
||||||
sway:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- sway
|
|
||||||
- waybar
|
|
||||||
- foot
|
|
||||||
- wofi
|
|
||||||
- nautilus
|
|
||||||
- greetd
|
|
||||||
- greetd-tuigreet
|
|
||||||
- xdg-user-dirs
|
|
||||||
- xdg-desktop-portal-wlr
|
|
||||||
- polkit-gnome
|
|
||||||
- bluez
|
|
||||||
hyprland:
|
|
||||||
groups: []
|
|
||||||
packages:
|
|
||||||
- hyprland
|
|
||||||
- kitty
|
|
||||||
- wofi
|
|
||||||
- waybar
|
|
||||||
- nautilus
|
|
||||||
- greetd
|
|
||||||
- greetd-tuigreet
|
|
||||||
- xdg-user-dirs
|
|
||||||
- xdg-desktop-portal-hyprland
|
|
||||||
- polkit-kde-agent
|
|
||||||
- qt5-wayland
|
|
||||||
- qt6-wayland
|
|
||||||
- bluez
|
|
||||||
|
|
||||||
# Installed for EVERY DE whenever desktop.enabled. No file manager here: DE metas
|
|
||||||
# bundle their own and the wlroots sets above carry nautilus.
|
|
||||||
bootstrap_desktop_base_packages:
|
|
||||||
RedHat:
|
|
||||||
- google-noto-sans-fonts
|
|
||||||
- google-noto-emoji-fonts
|
|
||||||
- "{{ bootstrap_desktop_redhat_codefont }}"
|
|
||||||
- pipewire
|
|
||||||
- wireplumber
|
|
||||||
- pipewire-pulseaudio
|
|
||||||
- xdg-desktop-portal
|
|
||||||
- "{{ bootstrap_desktop_power }}"
|
|
||||||
- bluez
|
|
||||||
- firefox
|
|
||||||
- "{{ bootstrap_desktop_pdf }}"
|
|
||||||
- "{{ bootstrap_desktop_image }}"
|
|
||||||
- "{{ bootstrap_desktop_redhat_video }}"
|
|
||||||
Debian:
|
|
||||||
- fonts-noto
|
|
||||||
- fonts-noto-color-emoji
|
|
||||||
- fonts-firacode
|
|
||||||
- pipewire
|
|
||||||
- wireplumber
|
|
||||||
- pipewire-pulse
|
|
||||||
- xdg-desktop-portal
|
|
||||||
- power-profiles-daemon
|
|
||||||
- bluez
|
|
||||||
- "{{ bootstrap_desktop_browser }}"
|
|
||||||
- evince
|
|
||||||
- eog
|
|
||||||
- mpv
|
|
||||||
Archlinux:
|
|
||||||
- noto-fonts
|
|
||||||
- noto-fonts-emoji
|
|
||||||
- ttf-nerd-fonts-symbols
|
|
||||||
- pipewire
|
|
||||||
- wireplumber
|
|
||||||
- pipewire-pulse
|
|
||||||
- xdg-desktop-portal
|
|
||||||
- power-profiles-daemon
|
|
||||||
- bluez
|
|
||||||
- firefox
|
|
||||||
- evince
|
|
||||||
- loupe
|
|
||||||
- mpv
|
|
||||||
|
|
||||||
# Opt-in groups selected per host via features.desktop.groups; the union of the
|
|
||||||
# requested groups' packages is installed. Empty selection by default.
|
|
||||||
desktop_package_groups:
|
|
||||||
dev:
|
|
||||||
RedHat:
|
|
||||||
- git
|
|
||||||
- "@development-tools"
|
|
||||||
- neovim
|
|
||||||
- python3-pip
|
|
||||||
Debian:
|
|
||||||
- git
|
|
||||||
- build-essential
|
|
||||||
- neovim
|
|
||||||
- python3-pip
|
|
||||||
Archlinux:
|
|
||||||
- git
|
|
||||||
- base-devel
|
|
||||||
- neovim
|
|
||||||
- python-pip
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
---
|
|
||||||
# Hardware-aware package definitions keyed by os_family, consumed by
|
|
||||||
# _resolve_hardware_packages.yml. Only packages matching detected hardware are
|
|
||||||
# installed; families without vendor splits collapse to one firmware meta package.
|
|
||||||
bootstrap_hardware_packages:
|
|
||||||
Archlinux:
|
|
||||||
cpu_microcode:
|
|
||||||
intel: [intel-ucode]
|
|
||||||
amd: [amd-ucode]
|
|
||||||
firmware_base: []
|
|
||||||
firmware:
|
|
||||||
intel: [linux-firmware-other] # iwlwifi + i915 firmware live here
|
|
||||||
amd: [linux-firmware-amdgpu]
|
|
||||||
nvidia: [linux-firmware-nvidia]
|
|
||||||
atheros: [linux-firmware-atheros]
|
|
||||||
broadcom: [linux-firmware-broadcom]
|
|
||||||
mediatek: [linux-firmware-mediatek]
|
|
||||||
marvell: [linux-firmware-marvell]
|
|
||||||
realtek: [linux-firmware-realtek]
|
|
||||||
qcom: [linux-firmware-qcom]
|
|
||||||
cirrus: [linux-firmware-cirrus]
|
|
||||||
other: [linux-firmware-other]
|
|
||||||
gpu_base: [mesa, vulkan-icd-loader]
|
|
||||||
gpu:
|
|
||||||
intel: [vulkan-intel, intel-media-driver]
|
|
||||||
amd: [vulkan-radeon, libva-mesa-driver]
|
|
||||||
gpu_nvidia:
|
|
||||||
open: [nvidia-open-dkms, nvidia-utils]
|
|
||||||
proprietary: [nvidia-dkms, nvidia-utils]
|
|
||||||
# Wayland-only: kernel nouveau module + mesa/gbm drive the display; no Xorg DDX.
|
|
||||||
nouveau: [vulkan-nouveau]
|
|
||||||
camera_base: [v4l-utils]
|
|
||||||
peripherals_fingerprint: [fprintd, libfprint]
|
|
||||||
peripherals_displaylink: [] # AUR only; user must wire in AUR helper
|
|
||||||
audio_base: [sof-firmware, alsa-ucm-conf]
|
|
||||||
bluetooth_base: [bluez, bluez-utils]
|
|
||||||
|
|
||||||
Debian:
|
|
||||||
cpu_microcode:
|
|
||||||
intel: [intel-microcode]
|
|
||||||
amd: [amd64-microcode]
|
|
||||||
firmware_base: [firmware-linux-free]
|
|
||||||
firmware:
|
|
||||||
intel: [firmware-iwlwifi, firmware-misc-nonfree]
|
|
||||||
amd: [firmware-amd-graphics, firmware-misc-nonfree]
|
|
||||||
nvidia: [firmware-misc-nonfree]
|
|
||||||
atheros: [firmware-atheros]
|
|
||||||
broadcom: [firmware-brcm80211]
|
|
||||||
mediatek: [firmware-misc-nonfree]
|
|
||||||
marvell: [firmware-misc-nonfree]
|
|
||||||
realtek: [firmware-realtek]
|
|
||||||
qcom: [firmware-misc-nonfree]
|
|
||||||
cirrus: [firmware-misc-nonfree]
|
|
||||||
other: [firmware-misc-nonfree]
|
|
||||||
gpu_base: [mesa-vulkan-drivers, libgl1-mesa-dri]
|
|
||||||
gpu:
|
|
||||||
intel: [intel-media-va-driver, i965-va-driver]
|
|
||||||
amd: [libva-glx2, mesa-va-drivers]
|
|
||||||
gpu_nvidia:
|
|
||||||
# Debian trixie+ ships nvidia-open-kernel-dkms; older releases only have
|
|
||||||
# the proprietary nvidia-driver. Both come from the non-free component.
|
|
||||||
open: [nvidia-open-kernel-dkms, nvidia-driver, nvidia-vulkan-icd]
|
|
||||||
proprietary: [nvidia-driver, nvidia-vulkan-icd]
|
|
||||||
# Wayland-only: kernel module + mesa (gpu_base) cover it; no Xorg DDX, no extra pkg.
|
|
||||||
nouveau: []
|
|
||||||
camera_base: [v4l-utils]
|
|
||||||
peripherals_fingerprint: [fprintd, libpam-fprintd]
|
|
||||||
peripherals_displaylink: [evdi-dkms] # userspace driver still needs vendor .run
|
|
||||||
audio_base: [firmware-sof-signed, alsa-ucm-conf]
|
|
||||||
bluetooth_base: [bluez]
|
|
||||||
|
|
||||||
RedHat:
|
|
||||||
cpu_microcode:
|
|
||||||
intel: [microcode_ctl]
|
|
||||||
amd: [microcode_ctl]
|
|
||||||
firmware_base: [linux-firmware]
|
|
||||||
firmware:
|
|
||||||
intel: []
|
|
||||||
amd: []
|
|
||||||
nvidia: []
|
|
||||||
atheros: []
|
|
||||||
broadcom: []
|
|
||||||
mediatek: []
|
|
||||||
marvell: []
|
|
||||||
realtek: []
|
|
||||||
qcom: []
|
|
||||||
cirrus: []
|
|
||||||
other: []
|
|
||||||
gpu_base: [mesa-dri-drivers, mesa-vulkan-drivers, vulkan-loader]
|
|
||||||
gpu:
|
|
||||||
intel: [intel-media-driver, libva-intel-driver]
|
|
||||||
amd: [mesa-va-drivers]
|
|
||||||
gpu_nvidia:
|
|
||||||
# akmod packages from RPMFusion non-free; repo enabled by _hardware.yml.
|
|
||||||
open: [akmod-nvidia-open, xorg-x11-drv-nvidia, xorg-x11-drv-nvidia-cuda]
|
|
||||||
proprietary: [akmod-nvidia, xorg-x11-drv-nvidia, xorg-x11-drv-nvidia-cuda]
|
|
||||||
# Wayland-only: kernel module + mesa (gpu_base) cover it; no Xorg DDX, no extra pkg.
|
|
||||||
nouveau: []
|
|
||||||
camera_base: [v4l-utils]
|
|
||||||
peripherals_fingerprint: [fprintd, fprintd-pam]
|
|
||||||
peripherals_displaylink: [evdi] # COPR-supplied; repo enablement deferred
|
|
||||||
audio_base: [alsa-sof-firmware, alsa-ucm]
|
|
||||||
bluetooth_base: [bluez]
|
|
||||||
@@ -1,266 +1,398 @@
|
|||||||
---
|
---
|
||||||
# Feature-gated packages shared across all distros. Arch strips nftables from
|
|
||||||
# this and composes it differently.
|
|
||||||
bootstrap_common_conditional: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(['firewalld'] if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else [])
|
|
||||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
|
||||||
+ (['iptables'] if system_cfg.features.firewall.toolkit == 'iptables' and system_cfg.features.firewall.enabled | bool else [])
|
|
||||||
+ (['nftables'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
|
||||||
+ (['cryptsetup', 'tpm2-tools'] if system_cfg.luks.enabled | bool else [])
|
|
||||||
+ (['qemu-guest-agent'] if hypervisor_type in ['libvirt', 'proxmox'] else [])
|
|
||||||
+ (['open-vm-tools'] if hypervisor_type == 'vmware' else [])
|
|
||||||
+ (['cloud-init'] if system_cfg.features.cloud_init | bool else [])
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
|
|
||||||
# Native-installer parity backfill: anaconda and the d-i "standard" task leave
|
|
||||||
# these, but install_weak_deps=False / Recommends-off minimal installs drop them.
|
|
||||||
bootstrap_el_runtime:
|
|
||||||
- NetworkManager
|
|
||||||
- authselect
|
|
||||||
- authselect-libs
|
|
||||||
- chrony
|
|
||||||
- crypto-policies
|
|
||||||
- crypto-policies-scripts
|
|
||||||
- dbus
|
|
||||||
- polkit
|
|
||||||
|
|
||||||
bootstrap_deb_runtime:
|
|
||||||
- apparmor-utils
|
|
||||||
- chrony
|
|
||||||
- libpam-pwquality
|
|
||||||
- needrestart
|
|
||||||
- network-manager
|
|
||||||
- sudo
|
|
||||||
|
|
||||||
# Per-OS package definitions: base (rootfs/group install), extra (post-base),
|
|
||||||
# conditional (feature/version-gated, appended by task files). DNF distros also
|
|
||||||
# carry repos and use base as group names.
|
|
||||||
bootstrap_rhel:
|
|
||||||
repos:
|
|
||||||
- "rhel{{ os_version_major }}-baseos"
|
|
||||||
- "rhel{{ os_version_major }}-appstream"
|
|
||||||
base:
|
|
||||||
- core
|
|
||||||
- base
|
|
||||||
- standard
|
|
||||||
extra:
|
|
||||||
- bind-utils
|
|
||||||
- efibootmgr
|
|
||||||
- glibc-langpack-de
|
|
||||||
- glibc-langpack-en
|
|
||||||
- grub2
|
|
||||||
- lrzsz
|
|
||||||
- lvm2
|
|
||||||
- mtr
|
|
||||||
- ncurses-term
|
|
||||||
- nfs-utils
|
|
||||||
- policycoreutils-python-utils
|
|
||||||
- shim
|
|
||||||
- tmux
|
|
||||||
- vim
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['grub2-efi-x64'] if os_version_major | default('') == '8' else ['grub2-efi'])
|
|
||||||
+ (['grub2-tools-extra'] if os_version_major | default('') in ['8', '9'] else [])
|
|
||||||
+ (['dhcp-client'] if (os_version_major | default('9') | int) < 10 else [])
|
|
||||||
+ (['python39'] if os_version_major | default('') == '8' else ['python'])
|
|
||||||
+ (['kernel'] if os_version_major | default('') == '10' else [])
|
|
||||||
+ (['zram-generator'] if os_version_major | default('') in ['9', '10'] else [])
|
|
||||||
+ bootstrap_el_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_almalinux:
|
bootstrap_almalinux:
|
||||||
repos:
|
- bind-utils
|
||||||
- baseos
|
- dbus-daemon
|
||||||
- appstream
|
- dhcp-client
|
||||||
base:
|
- efibootmgr
|
||||||
- core
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
extra:
|
- glibc-langpack-de
|
||||||
- bind-utils
|
- glibc-langpack-en
|
||||||
- efibootmgr
|
- grub2
|
||||||
- glibc-langpack-de
|
- grub2-efi
|
||||||
- glibc-langpack-en
|
- lrzsz
|
||||||
- grub2
|
- lvm2
|
||||||
- grub2-efi
|
- nc
|
||||||
- kernel
|
- nfs-utils
|
||||||
- lrzsz
|
- nfsv4-client-utils
|
||||||
- lvm2
|
- mtr
|
||||||
- mtr
|
- ppp
|
||||||
- nc
|
- shim
|
||||||
- ncurses-term
|
- tmux
|
||||||
- nfs-utils
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
- nfsv4-client-utils
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
- policycoreutils-python-utils
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
- ppp
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
- python3
|
- vim
|
||||||
- shim
|
- wget
|
||||||
- tmux
|
- zram-generator
|
||||||
- vim
|
- zstd
|
||||||
- zram-generator
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['dhcp-client'] if (os_version_major | default('10') | int) < 10 else [])
|
|
||||||
+ bootstrap_el_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_rocky:
|
bootstrap_archlinux:
|
||||||
repos:
|
- base
|
||||||
- baseos
|
- btrfs-progs
|
||||||
- appstream
|
- cronie
|
||||||
base:
|
- dhcpcd
|
||||||
- core
|
- efibootmgr
|
||||||
extra:
|
- fastfetch
|
||||||
- bind-utils
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
- efibootmgr
|
- fish
|
||||||
- glibc-langpack-de
|
- fzf
|
||||||
- glibc-langpack-en
|
- grub
|
||||||
- grub2
|
- htop
|
||||||
- grub2-efi
|
- libpwquality
|
||||||
- kernel
|
- linux
|
||||||
- lrzsz
|
- logrotate
|
||||||
- lvm2
|
- lrzsz
|
||||||
- mtr
|
- lsof
|
||||||
- nc
|
- lvm2
|
||||||
- ncurses-term
|
- ncdu
|
||||||
- nfs-utils
|
- networkmanager
|
||||||
- nfsv4-client-utils
|
- nfs-utils
|
||||||
- policycoreutils-python-utils
|
- openssh
|
||||||
- ppp
|
- ppp
|
||||||
- python3
|
- prometheus-node-exporter
|
||||||
- shim
|
- python-psycopg2
|
||||||
- telnet
|
- reflector
|
||||||
- tmux
|
- rsync
|
||||||
- util-linux-core
|
- sudo
|
||||||
- vim
|
- tldr
|
||||||
- wget
|
- tmux
|
||||||
- zram-generator
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
- zstd
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
conditional: >-
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
{{
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
(['dhcp-client'] if (os_version_major | default('9') | int) < 10 else [])
|
- vim
|
||||||
+ bootstrap_el_runtime
|
- wireguard-tools
|
||||||
+ bootstrap_common_conditional
|
- zram-generator
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_fedora:
|
bootstrap_debian11:
|
||||||
repos:
|
|
||||||
- fedora
|
|
||||||
- fedora-updates
|
|
||||||
base:
|
|
||||||
- critical-path-base
|
|
||||||
- core
|
|
||||||
extra:
|
|
||||||
- bat
|
|
||||||
- bind-utils
|
|
||||||
- btrfs-progs
|
|
||||||
- cronie
|
|
||||||
- dhcp-client
|
|
||||||
- duf
|
|
||||||
- efibootmgr
|
|
||||||
- entr
|
|
||||||
- fish
|
|
||||||
- fzf
|
|
||||||
- glibc-langpack-de
|
|
||||||
- glibc-langpack-en
|
|
||||||
- grub2
|
|
||||||
- grub2-efi
|
|
||||||
- htop
|
|
||||||
- iperf3
|
|
||||||
- logrotate
|
|
||||||
- lrzsz
|
|
||||||
- lvm2
|
|
||||||
- nc
|
|
||||||
- nfs-utils
|
|
||||||
- nfsv4-client-utils
|
|
||||||
- ppp
|
|
||||||
- python3
|
|
||||||
- ripgrep
|
|
||||||
- shim
|
|
||||||
- tmux
|
|
||||||
- vim-default-editor
|
|
||||||
- wget
|
|
||||||
- zoxide
|
|
||||||
- zram-generator
|
|
||||||
- zstd
|
|
||||||
conditional: "{{ bootstrap_el_runtime + bootstrap_common_conditional }}"
|
|
||||||
|
|
||||||
bootstrap_debian:
|
|
||||||
base:
|
base:
|
||||||
|
- apparmor-utils
|
||||||
- btrfs-progs
|
- btrfs-progs
|
||||||
|
- chrony
|
||||||
- cron
|
- cron
|
||||||
- cryptsetup-initramfs
|
|
||||||
- gnupg
|
- gnupg
|
||||||
- grub-efi
|
- grub-efi
|
||||||
- grub-efi-amd64-signed
|
- grub-efi-amd64-signed
|
||||||
- grub2-common
|
- grub2-common
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'cryptsetup-initramfs' if luks_enabled else '' }}"
|
||||||
|
- linux-image-amd64
|
||||||
- locales
|
- locales
|
||||||
- logrotate
|
- logrotate
|
||||||
- lvm2
|
- lvm2
|
||||||
|
- net-tools
|
||||||
- openssh-server
|
- openssh-server
|
||||||
- python3
|
- python3
|
||||||
|
- sudo
|
||||||
- xfsprogs
|
- xfsprogs
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
- bat
|
- bat
|
||||||
- curl
|
- curl
|
||||||
- entr
|
- entr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
- fish
|
- fish
|
||||||
- fzf
|
- fzf
|
||||||
- htop
|
- htop
|
||||||
- jq
|
- jq
|
||||||
- linux-image-amd64
|
- libpam-pwquality
|
||||||
- lrzsz
|
- lrzsz
|
||||||
- mtr
|
- mtr
|
||||||
- ncdu
|
- ncdu
|
||||||
- net-tools
|
- neofetch
|
||||||
|
- network-manager
|
||||||
- python-is-python3
|
- python-is-python3
|
||||||
- ripgrep
|
- ripgrep
|
||||||
- rsync
|
- rsync
|
||||||
- screen
|
- screen
|
||||||
|
- software-properties-common
|
||||||
- syslog-ng
|
- syslog-ng
|
||||||
- tcpd
|
- tcpd
|
||||||
|
- tldr
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
- vim
|
- vim
|
||||||
- wget
|
- wget
|
||||||
- zstd
|
- zstd
|
||||||
conditional: >-
|
|
||||||
{{
|
bootstrap_debian12:
|
||||||
(['duf'] if (os_version | string) not in ['10', '11'] else [])
|
base:
|
||||||
+ (['fastfetch'] if (os_version | string) in ['13', 'unstable'] else [])
|
- btrfs-progs
|
||||||
+ (['neofetch'] if (os_version | string) == '12' else [])
|
- cron
|
||||||
+ (['software-properties-common'] if (os_version | string) not in ['13', 'unstable'] else [])
|
- gnupg
|
||||||
+ (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else [])
|
- grub-efi
|
||||||
+ (['tldr'] if (os_version | string) not in ['13', 'unstable'] else [])
|
- grub-efi-amd64-signed
|
||||||
+ (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
- grub2-common
|
||||||
+ bootstrap_deb_runtime
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
+ bootstrap_common_conditional
|
- "{{ 'cryptsetup-initramfs' if luks_enabled else '' }}"
|
||||||
}}
|
- linux-image-amd64
|
||||||
|
- locales
|
||||||
|
- logrotate
|
||||||
|
- lvm2
|
||||||
|
- xfsprogs
|
||||||
|
|
||||||
|
extra:
|
||||||
|
- apparmor-utils
|
||||||
|
- bat
|
||||||
|
- chrony
|
||||||
|
- curl
|
||||||
|
- duf
|
||||||
|
- entr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- fish
|
||||||
|
- fzf
|
||||||
|
- htop
|
||||||
|
- jq
|
||||||
|
- libpam-pwquality
|
||||||
|
- logrotate
|
||||||
|
- lrzsz
|
||||||
|
- mtr
|
||||||
|
- ncdu
|
||||||
|
- neofetch
|
||||||
|
- net-tools
|
||||||
|
- network-manager
|
||||||
|
- openssh-server
|
||||||
|
- python-is-python3
|
||||||
|
- python3
|
||||||
|
- ripgrep
|
||||||
|
- rsync
|
||||||
|
- screen
|
||||||
|
- software-properties-common
|
||||||
|
- sudo
|
||||||
|
- syslog-ng
|
||||||
|
- systemd-zram-generator
|
||||||
|
- tcpd
|
||||||
|
- tldr
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_debian13:
|
||||||
|
base:
|
||||||
|
- btrfs-progs
|
||||||
|
- cron
|
||||||
|
- gnupg
|
||||||
|
- grub-efi
|
||||||
|
- grub-efi-amd64-signed
|
||||||
|
- grub2-common
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'cryptsetup-initramfs' if luks_enabled else '' }}"
|
||||||
|
- linux-image-amd64
|
||||||
|
- locales
|
||||||
|
- logrotate
|
||||||
|
- lvm2
|
||||||
|
- xfsprogs
|
||||||
|
|
||||||
|
extra:
|
||||||
|
- apparmor-utils
|
||||||
|
- bat
|
||||||
|
- chrony
|
||||||
|
- curl
|
||||||
|
- duf
|
||||||
|
- entr
|
||||||
|
- fastfetch
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- fish
|
||||||
|
- fzf
|
||||||
|
- htop
|
||||||
|
- jq
|
||||||
|
- libpam-pwquality
|
||||||
|
- logrotate
|
||||||
|
- lrzsz
|
||||||
|
- mtr
|
||||||
|
- ncdu
|
||||||
|
- net-tools
|
||||||
|
- network-manager
|
||||||
|
- openssh-server
|
||||||
|
- python-is-python3
|
||||||
|
- python3
|
||||||
|
- ripgrep
|
||||||
|
- rsync
|
||||||
|
- screen
|
||||||
|
- sudo
|
||||||
|
- syslog-ng
|
||||||
|
- systemd-zram-generator
|
||||||
|
- tcpd
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_fedora:
|
||||||
|
- bat
|
||||||
|
- bind-utils
|
||||||
|
- btrfs-progs
|
||||||
|
- cronie
|
||||||
|
- dhcp-client
|
||||||
|
- duf
|
||||||
|
- efibootmgr
|
||||||
|
- entr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- fish
|
||||||
|
- fzf
|
||||||
|
- glibc-langpack-de
|
||||||
|
- glibc-langpack-en
|
||||||
|
- grub2
|
||||||
|
- grub2-efi
|
||||||
|
- htop
|
||||||
|
- iperf3
|
||||||
|
- logrotate
|
||||||
|
- lrzsz
|
||||||
|
- lvm2
|
||||||
|
- nc
|
||||||
|
- nfs-utils
|
||||||
|
- nfsv4-client-utils
|
||||||
|
- polkit
|
||||||
|
- ppp
|
||||||
|
- ripgrep
|
||||||
|
- shim
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim-default-editor
|
||||||
|
- wget
|
||||||
|
- zoxide
|
||||||
|
- zram-generator
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_rhel8:
|
||||||
|
- bind-utils
|
||||||
|
- dhcp-client
|
||||||
|
- efibootmgr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- glibc-langpack-de
|
||||||
|
- glibc-langpack-en
|
||||||
|
- grub2
|
||||||
|
- grub2-efi-x64
|
||||||
|
- grub2-tools-extra
|
||||||
|
- lrzsz
|
||||||
|
- lvm2
|
||||||
|
- mtr
|
||||||
|
- ncurses-term
|
||||||
|
- nfs-utils
|
||||||
|
- policycoreutils-python-utils
|
||||||
|
- python39
|
||||||
|
- shim
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_rhel9:
|
||||||
|
- bind-utils
|
||||||
|
- dhcp-client
|
||||||
|
- efibootmgr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- glibc-langpack-de
|
||||||
|
- glibc-langpack-en
|
||||||
|
- grub2
|
||||||
|
- grub2-efi
|
||||||
|
- grub2-tools-extra
|
||||||
|
- lrzsz
|
||||||
|
- lvm2
|
||||||
|
- mtr
|
||||||
|
- ncurses-term
|
||||||
|
- nfs-utils
|
||||||
|
- policycoreutils-python-utils
|
||||||
|
- python
|
||||||
|
- shim
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- zram-generator
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_rhel10:
|
||||||
|
- bind-utils
|
||||||
|
- efibootmgr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- glibc-langpack-de
|
||||||
|
- glibc-langpack-en
|
||||||
|
- grub2
|
||||||
|
- grub2-efi
|
||||||
|
- kernel
|
||||||
|
- lrzsz
|
||||||
|
- lvm2
|
||||||
|
- mtr
|
||||||
|
- ncurses-term
|
||||||
|
- nfs-utils
|
||||||
|
- policycoreutils-python-utils
|
||||||
|
- python
|
||||||
|
- shim
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- zram-generator
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_rocky:
|
||||||
|
- bind-utils
|
||||||
|
- dbus-daemon
|
||||||
|
- dhcp-client
|
||||||
|
- efibootmgr
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
|
- glibc-langpack-de
|
||||||
|
- glibc-langpack-en
|
||||||
|
- grub2
|
||||||
|
- grub2-efi
|
||||||
|
- lrzsz
|
||||||
|
- lvm2
|
||||||
|
- mtr
|
||||||
|
- nc
|
||||||
|
- nfs-utils
|
||||||
|
- nfsv4-client-utils
|
||||||
|
- ppp
|
||||||
|
- shim
|
||||||
|
- telnet
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- util-linux-core
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- zram-generator
|
||||||
|
- zstd
|
||||||
|
|
||||||
bootstrap_ubuntu:
|
bootstrap_ubuntu:
|
||||||
base:
|
base:
|
||||||
- btrfs-progs
|
- btrfs-progs
|
||||||
- cron
|
- cron
|
||||||
- cryptsetup-initramfs
|
|
||||||
- gnupg
|
- gnupg
|
||||||
- grub-efi
|
- grub-efi
|
||||||
- grub-efi-amd64-signed
|
- grub-efi-amd64-signed
|
||||||
- grub2-common
|
- grub2-common
|
||||||
- initramfs-tools
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'cryptsetup-initramfs' if luks_enabled else '' }}"
|
||||||
- linux-image-generic
|
- linux-image-generic
|
||||||
- locales
|
- locales
|
||||||
- logrotate
|
|
||||||
- lvm2
|
- lvm2
|
||||||
- openssh-server
|
|
||||||
- python3
|
|
||||||
- xfsprogs
|
- xfsprogs
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
|
- apparmor-utils
|
||||||
- bash-completion
|
- bash-completion
|
||||||
- bat
|
- bat
|
||||||
|
- chrony
|
||||||
- curl
|
- curl
|
||||||
- dnsutils
|
- dnsutils
|
||||||
- duf
|
- duf
|
||||||
@@ -268,23 +400,34 @@ bootstrap_ubuntu:
|
|||||||
- eza
|
- eza
|
||||||
- fdupes
|
- fdupes
|
||||||
- fio
|
- fio
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
- fish
|
- fish
|
||||||
- fzf
|
|
||||||
- htop
|
- htop
|
||||||
- jq
|
- jq
|
||||||
|
- libpam-pwquality
|
||||||
|
- logrotate
|
||||||
- lrzsz
|
- lrzsz
|
||||||
- mtr
|
- mtr
|
||||||
- ncdu
|
- ncdu
|
||||||
- ncurses-term
|
- ncurses-term
|
||||||
- net-tools
|
- net-tools
|
||||||
|
- network-manager
|
||||||
|
- openssh-server
|
||||||
- python-is-python3
|
- python-is-python3
|
||||||
|
- python3
|
||||||
- ripgrep
|
- ripgrep
|
||||||
- rsync
|
- rsync
|
||||||
- screen
|
- screen
|
||||||
- software-properties-common
|
- software-properties-common
|
||||||
|
- sudo
|
||||||
- syslog-ng
|
- syslog-ng
|
||||||
- systemd-zram-generator
|
- systemd-zram-generator
|
||||||
- tcpd
|
- tcpd
|
||||||
|
- tldr
|
||||||
|
- tmux
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
- traceroute
|
- traceroute
|
||||||
- util-linux-extra
|
- util-linux-extra
|
||||||
- vim
|
- vim
|
||||||
@@ -292,48 +435,66 @@ bootstrap_ubuntu:
|
|||||||
- yq
|
- yq
|
||||||
- zoxide
|
- zoxide
|
||||||
- zstd
|
- zstd
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
|
||||||
+ bootstrap_deb_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_archlinux:
|
bootstrap_ubuntu_lts:
|
||||||
base:
|
base:
|
||||||
- base
|
|
||||||
- btrfs-progs
|
- btrfs-progs
|
||||||
- cronie
|
- cron
|
||||||
- dhcpcd
|
- gnupg
|
||||||
- efibootmgr
|
- grub-efi
|
||||||
- fastfetch
|
- grub-efi-amd64-signed
|
||||||
|
- grub2-common
|
||||||
|
- "{{ 'cryptsetup' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'cryptsetup-initramfs' if luks_enabled else '' }}"
|
||||||
|
- linux-image-generic
|
||||||
|
- locales
|
||||||
|
- lvm2
|
||||||
|
- xfsprogs
|
||||||
|
|
||||||
|
extra:
|
||||||
|
- apparmor-utils
|
||||||
|
- bash-completion
|
||||||
|
- bat
|
||||||
|
- chrony
|
||||||
|
- curl
|
||||||
|
- dnsutils
|
||||||
|
- duf
|
||||||
|
- entr
|
||||||
|
- eza
|
||||||
|
- fdupes
|
||||||
|
- fio
|
||||||
|
- "{{ 'firewalld' if firewalld_enabled | bool else '' }}"
|
||||||
- fish
|
- fish
|
||||||
- fzf
|
|
||||||
- grub
|
|
||||||
- htop
|
- htop
|
||||||
- libpwquality
|
- jq
|
||||||
- linux
|
- libpam-pwquality
|
||||||
- logrotate
|
- logrotate
|
||||||
- lrzsz
|
- lrzsz
|
||||||
- lsof
|
- mtr
|
||||||
- lvm2
|
|
||||||
- ncdu
|
- ncdu
|
||||||
- networkmanager
|
- ncurses-term
|
||||||
- nfs-utils
|
- net-tools
|
||||||
- ppp
|
- network-manager
|
||||||
- python
|
- openssh-server
|
||||||
|
- python-is-python3
|
||||||
|
- python3
|
||||||
|
- ripgrep
|
||||||
- rsync
|
- rsync
|
||||||
|
- screen
|
||||||
|
- software-properties-common
|
||||||
- sudo
|
- sudo
|
||||||
|
- syslog-ng
|
||||||
|
- systemd-zram-generator
|
||||||
|
- tcpd
|
||||||
- tldr
|
- tldr
|
||||||
- tmux
|
- tmux
|
||||||
|
- "{{ 'tpm2-tools' if luks_enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor | lower in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor | lower == 'vmware' else '' }}"
|
||||||
|
- traceroute
|
||||||
|
- util-linux-extra
|
||||||
- vim
|
- vim
|
||||||
- zram-generator
|
- wget
|
||||||
extra: []
|
- yq
|
||||||
conditional: >-
|
- zoxide
|
||||||
{{
|
- zstd
|
||||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
|
||||||
+ (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
|
||||||
+ (['sbctl'] if system_cfg.features.secure_boot.enabled | bool else [])
|
|
||||||
+ (['reflector'] if system_cfg.content.url | length == 0 else [])
|
|
||||||
+ (bootstrap_common_conditional | reject('equalto', 'nftables') | list)
|
|
||||||
}}
|
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
---
|
---
|
||||||
cis_permission_targets:
|
cis_permission_targets: >-
|
||||||
- {path: "/mnt/etc/ssh/sshd_config", mode: "0600"}
|
{{
|
||||||
- {path: "/mnt/etc/cron.hourly", mode: "0700"}
|
[
|
||||||
- {path: "/mnt/etc/cron.daily", mode: "0700"}
|
{ "path": "/mnt/etc/ssh/sshd_config", "mode": "0600" },
|
||||||
- {path: "/mnt/etc/cron.weekly", mode: "0700"}
|
{ "path": "/mnt/etc/cron.hourly", "mode": "0700" },
|
||||||
- {path: "/mnt/etc/cron.monthly", mode: "0700"}
|
{ "path": "/mnt/etc/cron.daily", "mode": "0700" },
|
||||||
- {path: "/mnt/etc/cron.d", mode: "0700"}
|
{ "path": "/mnt/etc/cron.weekly", "mode": "0700" },
|
||||||
- {path: "/mnt/etc/crontab", mode: "0600"}
|
{ "path": "/mnt/etc/cron.monthly", "mode": "0700" },
|
||||||
- {path: "/mnt/etc/logrotate.conf", mode: "0644"}
|
{ "path": "/mnt/etc/cron.d", "mode": "0700" },
|
||||||
- {path: "/mnt/usr/sbin/pppd", mode: "0754"}
|
{ "path": "/mnt/etc/crontab", "mode": "0600" },
|
||||||
- {path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755"}
|
{ "path": "/mnt/etc/logrotate.conf", "mode": "0644" },
|
||||||
- {path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755"}
|
{ "path": "/mnt/usr/sbin/pppd", "mode": "0754" } if os not in ["rhel8", "rhel9", "rhel10"] else None,
|
||||||
|
{
|
||||||
|
"path": "/mnt/usr/bin/"
|
||||||
|
+ ("fusermount3" if os in ["archlinux", "debian12", "fedora", "rhel9", "rhel10", "rocky"] else "fusermount"),
|
||||||
|
"mode": "755"
|
||||||
|
},
|
||||||
|
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian11" else "write"), "mode": "755" }
|
||||||
|
] | reject("none")
|
||||||
|
}}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Determine CIS profile
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cis_profile: "{{ system_cfg.features.cis.profile | default('default') }}"
|
|
||||||
|
|
||||||
- name: Validate CIS profile selection
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that: cis_profile in cis_profiles
|
|
||||||
fail_msg: >-
|
|
||||||
system.features.cis.profile '{{ cis_profile }}' is unknown
|
|
||||||
(valid: {{ cis_profiles.keys() | list | join(', ') }}).
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Resolve CIS rules and parameters
|
|
||||||
vars:
|
|
||||||
_cis: "{{ system_cfg.features.cis | default({}) }}"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cis_effective_rules: "{{ cis_profiles[cis_profile] | combine(_cis.rules | default({})) }}"
|
|
||||||
cis_cfg: >-
|
|
||||||
{{ cis_param_defaults
|
|
||||||
| combine(cis_profile_params[cis_profile] | default({}), recursive=True)
|
|
||||||
| combine(_cis.params | default({}), recursive=True) }}
|
|
||||||
# l1/l2 add the stricter CIS-server controls on top of the legacy `default`
|
|
||||||
# baseline; gate those tasks on this so `default` stays byte-for-byte unchanged.
|
|
||||||
cis_strict: "{{ cis_profile in ['l1', 'l2'] }}"
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install AIDE
|
|
||||||
when: cis_effective_rules.aide | default(false)
|
|
||||||
# Debian's aideinit lives in aide-common (only Recommended, so absent under
|
|
||||||
# the installer's --no-install-recommends); pull it explicitly.
|
|
||||||
ansible.builtin.command: "{{ cis_pkg_install }} {{ 'aide aide-common' if is_debian | bool else 'aide' }}"
|
|
||||||
register: cis_aide_install
|
|
||||||
changed_when: cis_aide_install.rc == 0
|
|
||||||
|
|
||||||
- name: Initialize the AIDE database
|
|
||||||
when: cis_effective_rules.aide | default(false)
|
|
||||||
# Absolute path: arch-chroot's PATH omits /usr/sbin, so bare aide/aideinit is rc127.
|
|
||||||
# Debian's aideinit assembles its split config; RHEL/Arch run --init on /etc/aide.conf.
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ '/usr/sbin/aideinit -y -f' if is_debian | bool else '/usr/sbin/aide --init' }}"
|
|
||||||
register: cis_aide_init
|
|
||||||
changed_when: cis_aide_init.rc == 0
|
|
||||||
|
|
||||||
- name: Locate the freshly built AIDE database
|
|
||||||
when: cis_effective_rules.aide | default(false)
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: /mnt/var/lib/aide
|
|
||||||
patterns: "aide.db.new*"
|
|
||||||
register: cis_aide_newdb
|
|
||||||
|
|
||||||
- name: Activate the AIDE database
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.aide | default(false)
|
|
||||||
- cis_aide_newdb.files | length > 0
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: "{{ cis_aide_newdb.files[0].path }}"
|
|
||||||
dest: "{{ cis_aide_newdb.files[0].path | regex_replace('\\.new', '') }}"
|
|
||||||
remote_src: true
|
|
||||||
mode: "0600"
|
|
||||||
|
|
||||||
- name: Schedule the daily AIDE integrity check
|
|
||||||
when: cis_effective_rules.aide | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/cron.d/cis-aide
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
|
||||||
{{ cis_cfg.aide_cron_minute }} {{ cis_cfg.aide_cron_hour }} * * * root aide --check
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install the audit daemon
|
|
||||||
when: cis_effective_rules.auditd | default(false)
|
|
||||||
ansible.builtin.command: "{{ cis_pkg_install }} {{ 'auditd' if is_debian | bool else 'audit' }}"
|
|
||||||
register: cis_auditd_install
|
|
||||||
changed_when: cis_auditd_install.rc == 0
|
|
||||||
|
|
||||||
- name: Deploy the CIS audit rule set
|
|
||||||
when: cis_effective_rules.auditd | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/audit/rules.d/cis.rules
|
|
||||||
mode: "0640"
|
|
||||||
content: |
|
|
||||||
## CIS baseline audit rules
|
|
||||||
-D
|
|
||||||
-b 8192
|
|
||||||
-f 1
|
|
||||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change
|
|
||||||
-w /etc/localtime -p wa -k time-change
|
|
||||||
-w /etc/group -p wa -k identity
|
|
||||||
-w /etc/passwd -p wa -k identity
|
|
||||||
-w /etc/shadow -p wa -k identity
|
|
||||||
-w /etc/gshadow -p wa -k identity
|
|
||||||
-w /etc/security/opasswd -p wa -k identity
|
|
||||||
-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
|
|
||||||
-w /etc/hosts -p wa -k system-locale
|
|
||||||
-w /var/log/lastlog -p wa -k logins
|
|
||||||
-w /var/run/faillock -p wa -k logins
|
|
||||||
-w /var/run/utmp -p wa -k session
|
|
||||||
-w /var/log/wtmp -p wa -k session
|
|
||||||
-w /var/log/btmp -p wa -k session
|
|
||||||
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod
|
|
||||||
-w /etc/sudoers -p wa -k scope
|
|
||||||
-w /etc/sudoers.d -p wa -k scope
|
|
||||||
-a always,exit -F arch=b64 -S init_module,delete_module -k modules
|
|
||||||
-e 2
|
|
||||||
|
|
||||||
- name: Enable the audit daemon
|
|
||||||
when: cis_effective_rules.auditd | default(false)
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable auditd"
|
|
||||||
register: cis_auditd_enable
|
|
||||||
changed_when: "'Created symlink' in cis_auditd_enable.stderr"
|
|
||||||
@@ -1,46 +1,15 @@
|
|||||||
---
|
---
|
||||||
- name: Ensure the Default UMASK is Set Correctly
|
- name: Ensure the Default UMASK is Set Correctly
|
||||||
when: cis_effective_rules.umask_default | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: "/mnt/etc/profile"
|
path: "/mnt/etc/profile"
|
||||||
regexp: "^(\\s*)umask\\s+\\d+"
|
regexp: "^(\\s*)umask\\s+\\d+"
|
||||||
line: "umask {{ cis_cfg.umask_profile }}"
|
line: "umask 027"
|
||||||
|
|
||||||
- name: Set the login.defs UMASK (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.umask_default | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/login.defs
|
|
||||||
regexp: '^\s*#?\s*UMASK\b'
|
|
||||||
line: "UMASK\t\t{{ cis_cfg.umask_profile }}"
|
|
||||||
|
|
||||||
# authselect regenerates system-auth from the profile, so a direct edit is lost
|
|
||||||
# on the next apply; without-nullok is the supported way to drop nullok there.
|
|
||||||
- name: Prevent Login to Accounts With Empty Password (authselect)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.empty_password_login | default(false)
|
|
||||||
- is_authselect | bool
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} authselect enable-feature without-nullok"
|
|
||||||
register: cis_nullok_result
|
|
||||||
changed_when: cis_nullok_result.rc == 0
|
|
||||||
|
|
||||||
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
|
|
||||||
- name: Prevent Login to Accounts With Empty Password
|
- name: Prevent Login to Accounts With Empty Password
|
||||||
when:
|
|
||||||
- cis_effective_rules.empty_password_login | default(false)
|
|
||||||
- not is_authselect | bool
|
|
||||||
ansible.builtin.replace:
|
ansible.builtin.replace:
|
||||||
dest: "{{ item }}"
|
dest: "{{ item }}"
|
||||||
regexp: "\\s*nullok"
|
regexp: "\\s*nullok"
|
||||||
replace: ""
|
replace: ""
|
||||||
loop: >-
|
loop:
|
||||||
{{
|
- /mnt/etc/pam.d/system-auth
|
||||||
['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth']
|
- /mnt/etc/pam.d/password-auth
|
||||||
if is_rhel | bool
|
|
||||||
else (
|
|
||||||
['/mnt/etc/pam.d/common-auth', '/mnt/etc/pam.d/common-password']
|
|
||||||
if is_debian | bool
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
---
|
---
|
||||||
# Fedora ships its own crypto-policies preset and update-crypto-policies
|
|
||||||
# behaves differently; applying DEFAULT:NO-SHA1 can break package signing.
|
|
||||||
# EL10 dropped the NO-SHA1 subpolicy module (DEFAULT already disables SHA-1
|
|
||||||
# signatures), so the modifier is set only on EL9 and below.
|
|
||||||
- name: Configure System Cryptography Policy
|
- name: Configure System Cryptography Policy
|
||||||
vars:
|
when: os in ["almalinux", "rhel9", "rhel10", "rocky"]
|
||||||
_cis_crypto_policy: "{{ 'DEFAULT' if (os_version_major | int >= 10) else 'DEFAULT:NO-SHA1' }}"
|
ansible.builtin.command: arch-chroot /mnt /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1
|
||||||
when:
|
|
||||||
- cis_effective_rules.crypto_policy | default(false)
|
|
||||||
- os in (os_family_rhel | difference(['fedora']))
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set {{ _cis_crypto_policy }}"
|
|
||||||
register: cis_crypto_policy_result
|
register: cis_crypto_policy_result
|
||||||
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
|
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
|
||||||
|
|
||||||
- name: Mask Systemd Services
|
- name: Mask Systemd Services
|
||||||
when: cis_effective_rules.mask_services | default(false)
|
|
||||||
ansible.builtin.command: >
|
ansible.builtin.command: >
|
||||||
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} bluetooth rpcbind
|
arch-chroot /mnt systemctl mask nftables bluetooth rpcbind
|
||||||
register: cis_mask_services_result
|
register: cis_mask_services_result
|
||||||
changed_when: "'Created symlink' in cis_mask_services_result.stderr"
|
changed_when: cis_mask_services_result.rc == 0
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Ensure cron and at access files exist
|
- name: Ensure files exist
|
||||||
when: cis_effective_rules.cron_at_access | default(false)
|
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ item }}"
|
path: "{{ item }}"
|
||||||
state: touch
|
state: touch
|
||||||
@@ -8,19 +7,10 @@
|
|||||||
loop:
|
loop:
|
||||||
- /mnt/etc/at.allow
|
- /mnt/etc/at.allow
|
||||||
- /mnt/etc/cron.allow
|
- /mnt/etc/cron.allow
|
||||||
|
|
||||||
- name: Ensure TCP wrapper files exist
|
|
||||||
when: cis_effective_rules.tcp_wrappers | default(false)
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: touch
|
|
||||||
mode: "0600"
|
|
||||||
loop:
|
|
||||||
- /mnt/etc/hosts.allow
|
- /mnt/etc/hosts.allow
|
||||||
- /mnt/etc/hosts.deny
|
- /mnt/etc/hosts.deny
|
||||||
|
|
||||||
- name: Ensure cron and at deny files do not exist
|
- name: Ensure files do not exist
|
||||||
when: cis_effective_rules.cron_at_access | default(false)
|
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ item }}"
|
path: "{{ item }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
# Opt-in only: a GRUB superuser password blocks unattended menu edits; the default entry still boots.
|
|
||||||
- name: Assert a GRUB password hash is supplied
|
|
||||||
when: cis_effective_rules.grub_password | default(false)
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that: cis_cfg.grub_password_hash | length > 0
|
|
||||||
fail_msg: >-
|
|
||||||
system.features.cis.rules.grub_password is enabled but
|
|
||||||
system.features.cis.params.grub_password_hash is empty. Generate one with
|
|
||||||
grub2-mkpasswd-pbkdf2 and set it there.
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Deploy the GRUB superuser password
|
|
||||||
when: cis_effective_rules.grub_password | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/grub.d/01_cis_password
|
|
||||||
mode: "0755"
|
|
||||||
content: |
|
|
||||||
#!/bin/sh
|
|
||||||
cat <<'EOF'
|
|
||||||
set superusers="root"
|
|
||||||
password_pbkdf2 root {{ cis_cfg.grub_password_hash }}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Regenerate the GRUB configuration
|
|
||||||
when: cis_effective_rules.grub_password | default(false)
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }}
|
|
||||||
{{ 'grub2-mkconfig -o /boot/grub2/grub.cfg' if is_rhel | bool else 'grub-mkconfig -o /boot/grub/grub.cfg' }}
|
|
||||||
register: cis_grub_regen
|
|
||||||
changed_when: cis_grub_regen.rc == 0
|
|
||||||
@@ -1,25 +1,14 @@
|
|||||||
---
|
---
|
||||||
- name: Normalize CIS configuration
|
- name: Include CIS hardening tasks
|
||||||
ansible.builtin.import_tasks: _normalize.yml
|
ansible.builtin.include_tasks: "{{ cis_task }}"
|
||||||
|
loop:
|
||||||
- name: Apply CIS hardening
|
- modules.yml
|
||||||
block:
|
- sysctl.yml
|
||||||
- name: Include CIS hardening tasks
|
- auth.yml
|
||||||
ansible.builtin.include_tasks: "{{ cis_task }}"
|
- crypto.yml
|
||||||
loop:
|
- files.yml
|
||||||
- modules.yml
|
- security_lines.yml
|
||||||
- sysctl.yml
|
- permissions.yml
|
||||||
- auth.yml
|
- sshd.yml
|
||||||
- crypto.yml
|
loop_control:
|
||||||
- files.yml
|
loop_var: cis_task
|
||||||
- security_lines.yml
|
|
||||||
- permissions.yml
|
|
||||||
- sshd.yml
|
|
||||||
- warning_banners.yml
|
|
||||||
- password_expiry.yml
|
|
||||||
- aide.yml
|
|
||||||
- auditd.yml
|
|
||||||
- packages.yml
|
|
||||||
- grub_password.yml
|
|
||||||
loop_control:
|
|
||||||
loop_var: cis_task
|
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
---
|
---
|
||||||
- name: Disable Kernel Modules
|
- name: Disable Kernel Modules
|
||||||
when: cis_effective_rules.module_blacklist | default(false)
|
|
||||||
vars:
|
|
||||||
# Ubuntu uses squashfs for snap packages - blacklisting it breaks snap entirely
|
|
||||||
cis_modules_squashfs: "{{ [] if os in ['ubuntu', 'ubuntu-lts'] else ['squashfs'] }}"
|
|
||||||
cis_modules_all: "{{ cis_cfg.modules_blacklist + cis_modules_squashfs }}"
|
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /mnt/etc/modprobe.d/cis.conf
|
dest: /mnt/etc/modprobe.d/cis.conf
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
content: |
|
content: |
|
||||||
# CIS LVL 3 Restrictions
|
# CIS LVL 3 Restrictions
|
||||||
{% for mod in cis_modules_all %}
|
install freevxfs /bin/false
|
||||||
install {{ mod }}{{ ' ' * (16 - mod | length) }}/bin/false
|
install jffs2 /bin/false
|
||||||
{% endfor %}
|
install hfs /bin/false
|
||||||
|
install hfsplus /bin/false
|
||||||
|
install cramfs /bin/false
|
||||||
|
install squashfs /bin/false
|
||||||
|
install udf /bin/false
|
||||||
|
install usb-storage /bin/false
|
||||||
|
install dccp /bin/false
|
||||||
|
install sctp /bin/false
|
||||||
|
install rds /bin/false
|
||||||
|
install tipc /bin/false
|
||||||
|
|
||||||
- name: Remove old USB rules file
|
- name: Remove legacy USB rules file
|
||||||
when: cis_effective_rules.usb_lockdown | default(false)
|
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
|
path: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Create USB rules
|
- name: Create USB rules
|
||||||
when: cis_effective_rules.usb_lockdown | default(false)
|
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.rules
|
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.rules
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
# CIS L1 names legacy cleartext clients (telnet) for removal. They are absent on
|
|
||||||
# a fresh minimal install; query first and remove only when present so the run
|
|
||||||
# stays idempotent (a chroot package-manager remove cannot use the package module).
|
|
||||||
- name: Check for insecure cleartext clients
|
|
||||||
when: cis_strict | default(false)
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }}
|
|
||||||
{{ 'dpkg -s' if is_debian | bool else 'pacman -Q' if os == 'archlinux' else 'rpm -q' }}
|
|
||||||
{{ item }}
|
|
||||||
loop: "{{ cis_cfg.insecure_packages }}"
|
|
||||||
register: cis_insecure_present
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
|
|
||||||
- name: Remove insecure cleartext clients (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_strict | default(false)
|
|
||||||
- item.rc == 0
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }}
|
|
||||||
{{ 'apt-get remove -y' if is_debian | bool else 'pacman -R --noconfirm' if os == 'archlinux' else 'dnf remove -y' }}
|
|
||||||
{{ item.item }}
|
|
||||||
loop: "{{ cis_insecure_present.results | default([]) }}"
|
|
||||||
changed_when: true
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.item }}"
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
# login.defs sets policy for future accounts; existing service accounts are intentionally not chage-aged.
|
|
||||||
- name: Configure password aging defaults
|
|
||||||
when: cis_effective_rules.password_expiry | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/login.defs
|
|
||||||
regexp: '^#?\s*{{ item.key }}\b'
|
|
||||||
line: "{{ item.key }}\t{{ item.value }}"
|
|
||||||
loop:
|
|
||||||
- {key: PASS_MAX_DAYS, value: "{{ cis_cfg.pass_max_days }}"}
|
|
||||||
- {key: PASS_MIN_DAYS, value: "{{ cis_cfg.pass_min_days }}"}
|
|
||||||
- {key: PASS_WARN_AGE, value: "{{ cis_cfg.pass_warn_age }}"}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.key }}"
|
|
||||||
|
|
||||||
# account_disable_post_pw_expiration: lock accounts INACTIVE days after expiry.
|
|
||||||
- name: Set the default account inactivity lock period
|
|
||||||
when: cis_effective_rules.password_expiry | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/default/useradd
|
|
||||||
regexp: '^\s*#?\s*INACTIVE\s*='
|
|
||||||
line: "INACTIVE={{ cis_cfg.pass_inactive }}"
|
|
||||||
@@ -1,23 +1,16 @@
|
|||||||
---
|
---
|
||||||
- name: Check CIS permission targets
|
- name: Check CIS permission targets
|
||||||
when: cis_effective_rules.file_permissions | default(false)
|
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "{{ item.path }}"
|
path: "{{ item.path }}"
|
||||||
loop: "{{ cis_permission_targets }}"
|
loop: "{{ cis_permission_targets }}"
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
register: cis_permission_stats
|
register: cis_permission_stats
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Set permissions for existing targets
|
- name: Set permissions for existing targets
|
||||||
when:
|
|
||||||
- cis_effective_rules.file_permissions | default(false)
|
|
||||||
- item.stat.exists
|
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ item.item.path }}"
|
path: "{{ item.item.path }}"
|
||||||
owner: "{{ item.item.owner | default(omit) }}"
|
owner: "{{ item.item.owner | default(omit) }}"
|
||||||
group: "{{ item.item.group | default(omit) }}"
|
group: "{{ item.item.group | default(omit) }}"
|
||||||
mode: "{{ item.item.mode }}"
|
mode: "{{ item.item.mode }}"
|
||||||
loop: "{{ cis_permission_stats.results | default([]) }}"
|
loop: "{{ cis_permission_stats.results }}"
|
||||||
loop_control:
|
when: item.stat.exists
|
||||||
label: "{{ item.item.path }}"
|
|
||||||
|
|||||||
@@ -1,218 +1,46 @@
|
|||||||
---
|
---
|
||||||
- name: Restrict core dumps
|
- name: Add Security related lines into config files
|
||||||
when: cis_effective_rules.core_dumps | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/security/limits.conf
|
|
||||||
regexp: '^\*\s+hard\s+core\s+'
|
|
||||||
line: "* hard core 0"
|
|
||||||
|
|
||||||
- name: Ensure the systemd coredump drop-in directory exists (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.core_dumps | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/systemd/coredump.conf.d
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Disable systemd core dump storage and backtraces (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.core_dumps | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/systemd/coredump.conf.d/10-cis.conf
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
[Coredump]
|
|
||||||
Storage=none
|
|
||||||
ProcessSizeMax=0
|
|
||||||
|
|
||||||
- name: Set password quality requirements
|
|
||||||
when: cis_effective_rules.pwquality | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/security/pwquality.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
loop:
|
|
||||||
- {regexp: '^\s*#?\s*minlen\s*=', line: "minlen = {{ cis_cfg.pwquality_minlen }}"}
|
|
||||||
- {regexp: '^\s*#?\s*dcredit\s*=', line: "dcredit = -1"}
|
|
||||||
- {regexp: '^\s*#?\s*ucredit\s*=', line: "ucredit = -1"}
|
|
||||||
- {regexp: '^\s*#?\s*ocredit\s*=', line: "ocredit = -1"}
|
|
||||||
- {regexp: '^\s*#?\s*lcredit\s*=', line: "lcredit = -1"}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.line }}"
|
|
||||||
|
|
||||||
# Stricter complexity SSG cis_server_l1 checks; affects only new-password changes.
|
|
||||||
- name: Set strict password quality requirements (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.pwquality | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/security/pwquality.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
loop:
|
|
||||||
- {regexp: '^\s*#?\s*difok\s*=', line: "difok = {{ cis_cfg.pwquality_difok }}"}
|
|
||||||
- {regexp: '^\s*#?\s*maxrepeat\s*=', line: "maxrepeat = {{ cis_cfg.pwquality_maxrepeat }}"}
|
|
||||||
- {regexp: '^\s*#?\s*maxsequence\s*=', line: "maxsequence = {{ cis_cfg.pwquality_maxsequence }}"}
|
|
||||||
- {regexp: '^\s*#?\s*minclass\s*=', line: "minclass = {{ cis_cfg.pwquality_minclass }}"}
|
|
||||||
- {regexp: '^\s*#?\s*dictcheck\s*=', line: "dictcheck = {{ cis_cfg.pwquality_dictcheck }}"}
|
|
||||||
- {regexp: '^\s*#?\s*enforce_for_root\b', line: "enforce_for_root"}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.line }}"
|
|
||||||
|
|
||||||
- name: Set the default shell umask
|
|
||||||
when: cis_effective_rules.umask_default | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
|
||||||
regexp: '^\s*umask\s+\d+'
|
|
||||||
line: "umask {{ cis_cfg.umask }}"
|
|
||||||
|
|
||||||
- name: Set the shell idle timeout
|
|
||||||
when: cis_effective_rules.shell_timeout | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
|
||||||
regexp: '^\s*(export\s+)?TMOUT='
|
|
||||||
line: "export TMOUT={{ cis_cfg.tmout }}"
|
|
||||||
|
|
||||||
# A drop-in survives systemd upgrades; the RHEL vendor journald.conf does not.
|
|
||||||
- name: Ensure the journald drop-in directory exists
|
|
||||||
when: cis_effective_rules.journald_persistent | default(false)
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/systemd/journald.conf.d
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Enable persistent journald storage
|
|
||||||
when: cis_effective_rules.journald_persistent | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/systemd/journald.conf.d/10-cis.conf
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
[Journal]
|
|
||||||
Storage=persistent
|
|
||||||
|
|
||||||
- name: Compress large journald log files (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.journald_persistent | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/systemd/journald.conf.d/20-cis-compress.conf
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
[Journal]
|
|
||||||
Compress=yes
|
|
||||||
|
|
||||||
- name: Log sudo commands
|
|
||||||
when: cis_effective_rules.sudo_logfile | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/sudoers
|
|
||||||
regexp: '^\s*Defaults\s+logfile='
|
|
||||||
line: 'Defaults logfile="/var/log/sudo.log"'
|
|
||||||
|
|
||||||
- name: Require a pty for sudo (CIS L1+)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.sudo_logfile | default(false)
|
|
||||||
- cis_strict | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/sudoers
|
|
||||||
regexp: '^\s*Defaults\s+use_pty\b'
|
|
||||||
line: "Defaults use_pty"
|
|
||||||
|
|
||||||
- name: Restrict su to the wheel group
|
|
||||||
when: cis_effective_rules.su_restriction | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/pam.d/su
|
|
||||||
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
|
|
||||||
line: auth required pam_wheel.so
|
|
||||||
|
|
||||||
# authselect wires the pam_faillock stack via the feature; deny/unlock_time live
|
|
||||||
# in faillock.conf, the supported place (pam_faillock(8) deprecates module args).
|
|
||||||
- name: Configure account lockout (authselect)
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.faillock | default(false)
|
|
||||||
- is_authselect | bool
|
|
||||||
block:
|
|
||||||
- name: Enable the authselect faillock feature
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} authselect enable-feature with-faillock"
|
|
||||||
register: cis_faillock_result
|
|
||||||
changed_when: cis_faillock_result.rc == 0
|
|
||||||
|
|
||||||
- name: Set faillock thresholds
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/security/faillock.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
create: true
|
|
||||||
mode: "0644"
|
|
||||||
loop:
|
|
||||||
- {regexp: '^\s*#?\s*deny\s*=', line: "deny = {{ cis_cfg.faillock_deny }}"}
|
|
||||||
- {regexp: '^\s*#?\s*unlock_time\s*=', line: "unlock_time = {{ cis_cfg.faillock_unlock_time }}"}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.line }}"
|
|
||||||
|
|
||||||
- name: Configure account lockout
|
|
||||||
when:
|
|
||||||
- cis_effective_rules.faillock | default(false)
|
|
||||||
- not is_authselect | bool
|
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: "{{ item.path }}"
|
path: "{{ item.path }}"
|
||||||
regexp: "{{ item.regexp }}"
|
line: "{{ item.content }}"
|
||||||
line: "{{ item.line }}"
|
|
||||||
loop:
|
loop:
|
||||||
- path: '/mnt/etc/{{ "pam.d/common-auth" if is_debian | bool else "pam.d/system-auth" }}'
|
- {path: /mnt/etc/security/limits.conf, content: "* hard core 0"}
|
||||||
regexp: '^\s*auth\s+required\s+pam_faillock\.so'
|
- {path: /mnt/etc/security/pwquality.conf, content: minlen = 14}
|
||||||
line: >-
|
- {path: /mnt/etc/security/pwquality.conf, content: dcredit = -1}
|
||||||
auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }}
|
- {path: /mnt/etc/security/pwquality.conf, content: ucredit = -1}
|
||||||
- path: '/mnt/etc/{{ "pam.d/common-account" if is_debian | bool else "pam.d/system-auth" }}'
|
- {path: /mnt/etc/security/pwquality.conf, content: ocredit = -1}
|
||||||
regexp: '^\s*account\s+required\s+pam_faillock\.so'
|
- {path: /mnt/etc/security/pwquality.conf, content: lcredit = -1}
|
||||||
line: account required pam_faillock.so
|
- {path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: umask 077}
|
||||||
loop_control:
|
- {path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: export TMOUT=3000}
|
||||||
label: "{{ item.regexp }}"
|
- {path: '/mnt/{{ "usr/lib/systemd/journald.conf" if os == "fedora" else "etc/systemd/journald.conf" }}', content: Storage=persistent}
|
||||||
|
- {path: /mnt/etc/sudoers, content: Defaults logfile="/var/log/sudo.log"}
|
||||||
- name: Enforce password history
|
- {path: /mnt/etc/pam.d/su, content: auth required pam_wheel.so}
|
||||||
when: cis_effective_rules.password_history | default(false)
|
- path: >-
|
||||||
ansible.builtin.lineinfile:
|
/mnt/etc/{{
|
||||||
path: >-
|
"pam.d/common-auth"
|
||||||
/mnt/etc/pam.d/{{
|
if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
|
||||||
"common-password"
|
else "authselect/system-auth"
|
||||||
if is_debian | bool
|
if os == "fedora"
|
||||||
else "passwd"
|
else "pam.d/system-auth"
|
||||||
}}
|
}}
|
||||||
regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so'
|
content: >-
|
||||||
line: >-
|
auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900
|
||||||
password [success=1 default=ignore] pam_unix.so obscure sha512 remember={{ cis_cfg.password_remember }}
|
- path: >-
|
||||||
|
/mnt/etc/{{
|
||||||
# SSG cis_server_l1 checks pam_pwhistory (not pam_unix remember) in the auth-stack
|
"pam.d/common-account"
|
||||||
# files; affects only password changes, so no login-lockout risk. EL9 has no
|
if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
|
||||||
# authselect path here (same direct-edit the faillock rule above uses).
|
else "authselect/system-auth"
|
||||||
- name: Enforce password reuse limit via pam_pwhistory (CIS L1+)
|
if os == "fedora"
|
||||||
when:
|
else "pam.d/system-auth"
|
||||||
- cis_effective_rules.password_history | default(false)
|
}}
|
||||||
- cis_strict | default(false)
|
content: account required pam_faillock.so
|
||||||
ansible.builtin.lineinfile:
|
- path: >-
|
||||||
path: "{{ item }}"
|
/mnt/etc/pam.d/{{
|
||||||
regexp: '^\s*password\s+(requisite|required)\s+pam_pwhistory\.so'
|
"common-password"
|
||||||
line: "password requisite pam_pwhistory.so use_authtok remember={{ cis_cfg.pwhistory_remember }} enforce_for_root"
|
if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
|
||||||
insertbefore: '^\s*password\s+.*pam_unix\.so'
|
else "passwd"
|
||||||
loop: >-
|
}}
|
||||||
{{
|
content: >-
|
||||||
['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth']
|
password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5
|
||||||
if is_rhel | bool
|
- {path: /mnt/etc/hosts.deny, content: "ALL: ALL"}
|
||||||
else (['/mnt/etc/pam.d/common-password'] if is_debian | bool else [])
|
- {path: /mnt/etc/hosts.allow, content: "sshd: ALL"}
|
||||||
}}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
|
|
||||||
|
|
||||||
- name: Configure TCP wrappers
|
|
||||||
when: cis_effective_rules.tcp_wrappers | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: "{{ item.path }}"
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
loop:
|
|
||||||
- {path: /mnt/etc/hosts.deny, regexp: '^ALL:\s*ALL', line: "ALL: ALL"}
|
|
||||||
- {path: /mnt/etc/hosts.allow, regexp: '^sshd:\s*ALL', line: "sshd: ALL"}
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
|
|||||||
@@ -1,43 +1,51 @@
|
|||||||
---
|
---
|
||||||
- name: Adjust SSHD config
|
- name: Adjust SSHD config
|
||||||
when: cis_effective_rules.sshd_hardening | default(false)
|
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/ssh/sshd_config
|
path: /mnt/etc/ssh/sshd_config
|
||||||
regexp: ^\s*#?{{ item.option }}\s+.*$
|
regexp: ^\s*#?{{ item.option }}\s+.*$
|
||||||
line: "{{ item.option }} {{ item.value }}"
|
line: "{{ item.option }} {{ item.value }}"
|
||||||
loop: "{{ cis_cfg.sshd_options }}"
|
loop:
|
||||||
loop_control:
|
- {option: LogLevel, value: VERBOSE}
|
||||||
label: "{{ item.option }}"
|
- {option: LoginGraceTime, value: "60"}
|
||||||
|
- {option: PermitRootLogin, value: "no"}
|
||||||
- name: Detect target OpenSSH version
|
- {option: StrictModes, value: "yes"}
|
||||||
when: cis_effective_rules.sshd_hardening | default(false)
|
- {option: MaxAuthTries, value: "4"}
|
||||||
ansible.builtin.shell: >-
|
- {option: MaxSessions, value: "10"}
|
||||||
set -o pipefail && {{ chroot_command }} ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9]+\.[0-9]+'
|
- {option: MaxStartups, value: "10:30:60"}
|
||||||
args:
|
- {option: PubkeyAuthentication, value: "yes"}
|
||||||
executable: /bin/bash
|
- {option: HostbasedAuthentication, value: "no"}
|
||||||
register: cis_sshd_openssh_version
|
- {option: IgnoreRhosts, value: "yes"}
|
||||||
changed_when: false
|
- {option: PasswordAuthentication, value: "no"}
|
||||||
failed_when: false
|
- {option: PermitEmptyPasswords, value: "no"}
|
||||||
|
- {option: KerberosAuthentication, value: "no"}
|
||||||
|
- {option: GSSAPIAuthentication, value: "no"}
|
||||||
|
- {option: AllowAgentForwarding, value: "no"}
|
||||||
|
- {option: AllowTcpForwarding, value: "no"}
|
||||||
|
- {option: ChallengeResponseAuthentication, value: "no"}
|
||||||
|
- {option: GatewayPorts, value: "no"}
|
||||||
|
- {option: X11Forwarding, value: "no"}
|
||||||
|
- {option: PermitUserEnvironment, value: "no"}
|
||||||
|
- {option: ClientAliveInterval, value: "300"}
|
||||||
|
- {option: ClientAliveCountMax, value: "1"}
|
||||||
|
- {option: PermitTunnel, value: "no"}
|
||||||
|
- {option: Banner, value: /etc/issue.net}
|
||||||
|
|
||||||
- name: Append CIS specific configurations to sshd_config
|
- name: Append CIS specific configurations to sshd_config
|
||||||
when: cis_effective_rules.sshd_hardening | default(false)
|
|
||||||
vars:
|
|
||||||
cis_sshd_has_mlkem: "{{ (cis_sshd_openssh_version.stdout | default('0.0') is version('9.9', '>=')) }}"
|
|
||||||
cis_sshd_kex: >-
|
|
||||||
{{
|
|
||||||
(['mlkem768x25519-sha256'] if cis_sshd_has_mlkem | bool else [])
|
|
||||||
+ ['curve25519-sha256@libssh.org', 'ecdh-sha2-nistp521', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp256']
|
|
||||||
}}
|
|
||||||
ansible.builtin.blockinfile:
|
ansible.builtin.blockinfile:
|
||||||
path: /mnt/etc/ssh/sshd_config
|
path: /mnt/etc/ssh/sshd_config
|
||||||
marker: "# {mark} CIS SSH HARDENING"
|
marker: "# {mark} CIS SSH HARDENING"
|
||||||
block: |-
|
block: |-
|
||||||
## CIS Specific
|
## CIS Specific
|
||||||
|
Protocol 2
|
||||||
### Ciphers and keying ###
|
### Ciphers and keying ###
|
||||||
RekeyLimit 512M 6h
|
RekeyLimit 512M 6h
|
||||||
KexAlgorithms {{ cis_sshd_kex | join(',') }}
|
KexAlgorithms mlkem768x25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
|
||||||
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
|
||||||
###########################
|
###########################
|
||||||
AllowStreamLocalForwarding no
|
AllowStreamLocalForwarding no
|
||||||
PermitUserRC no
|
PermitUserRC no
|
||||||
|
AllowUsers *
|
||||||
|
AllowGroups *
|
||||||
|
DenyUsers nobody
|
||||||
|
DenyGroups nobody
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
---
|
---
|
||||||
- name: Create a consolidated sysctl configuration file
|
- name: Create a consolidated sysctl configuration file
|
||||||
when: cis_effective_rules.sysctl_hardening | default(false)
|
|
||||||
vars:
|
|
||||||
# ipv6_disable is a separate rule: when off, drop the disable_ipv6 keys but keep the rest.
|
|
||||||
_cis_sysctl: >-
|
|
||||||
{{ cis_cfg.sysctl
|
|
||||||
if (cis_effective_rules.ipv6_disable | default(false))
|
|
||||||
else (cis_cfg.sysctl | dict2items | rejectattr('key', 'search', 'disable_ipv6') | items2dict) }}
|
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
# 99- so CIS wins: a 10- name loses to vendor /usr/lib/sysctl.d/10-default-yama-scope.conf
|
dest: /mnt/etc/sysctl.d/10-cis.conf
|
||||||
# (later basename applies last), which reset kernel.yama.ptrace_scope back to 0.
|
|
||||||
dest: /mnt/etc/sysctl.d/99-cis.conf
|
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
content: |
|
content: |
|
||||||
## CIS Sysctl configurations
|
## CIS Sysctl configurations
|
||||||
{% for key, value in _cis_sysctl | dictsort %}
|
kernel.yama.ptrace_scope=1
|
||||||
{{ key }}={{ value }}
|
kernel.randomize_va_space=2
|
||||||
{% endfor %}
|
# Network
|
||||||
|
net.ipv4.ip_forward=0
|
||||||
|
net.ipv4.tcp_syncookies=1
|
||||||
|
net.ipv4.icmp_echo_ignore_broadcasts=1
|
||||||
|
net.ipv4.icmp_ignore_bogus_error_responses=1
|
||||||
|
net.ipv4.conf.all.log_martians = 1
|
||||||
|
net.ipv4.conf.all.rp_filter = 1
|
||||||
|
net.ipv4.conf.all.secure_redirects = 0
|
||||||
|
net.ipv4.conf.all.send_redirects = 0
|
||||||
|
net.ipv4.conf.all.accept_redirects = 0
|
||||||
|
net.ipv4.conf.all.accept_source_route=0
|
||||||
|
net.ipv4.conf.default.log_martians = 1
|
||||||
|
net.ipv4.conf.default.rp_filter = 1
|
||||||
|
net.ipv4.conf.default.secure_redirects = 0
|
||||||
|
net.ipv4.conf.default.send_redirects = 0
|
||||||
|
net.ipv4.conf.default.accept_redirects = 0
|
||||||
|
net.ipv6.conf.all.accept_redirects = 0
|
||||||
|
net.ipv6.conf.all.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.default.accept_redirects = 0
|
||||||
|
net.ipv6.conf.default.disable_ipv6 = 1
|
||||||
|
net.ipv6.conf.lo.disable_ipv6 = 1
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Set login warning banners
|
|
||||||
when: cis_effective_rules.warning_banners | default(false)
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: "/mnt/etc/{{ item }}"
|
|
||||||
content: "{{ cis_cfg.banner_text }}\n"
|
|
||||||
mode: "0644"
|
|
||||||
loop:
|
|
||||||
- issue
|
|
||||||
- issue.net
|
|
||||||
- motd
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
---
|
|
||||||
# fusermount3 is the modern name; older distros still ship fusermount.
|
|
||||||
cis_fusermount_binary: >-
|
|
||||||
{{
|
|
||||||
'fusermount3'
|
|
||||||
if (
|
|
||||||
os in ['archlinux', 'fedora', 'rocky', 'rhel']
|
|
||||||
or (os == 'debian' and (os_version | string) not in ['10', '11'])
|
|
||||||
or (os == 'almalinux')
|
|
||||||
)
|
|
||||||
else 'fusermount'
|
|
||||||
}}
|
|
||||||
|
|
||||||
# write.ul is the Debian 11 name; all others use write.
|
|
||||||
cis_write_binary: >-
|
|
||||||
{{
|
|
||||||
'write.ul'
|
|
||||||
if (os == 'debian' and (os_version | string) == '11')
|
|
||||||
else 'write'
|
|
||||||
}}
|
|
||||||
|
|
||||||
cis_pkg_install: >-
|
|
||||||
{{ chroot_command }} {{
|
|
||||||
'apt-get install -y'
|
|
||||||
if is_debian | bool
|
|
||||||
else 'pacman -S --noconfirm'
|
|
||||||
if os == 'archlinux'
|
|
||||||
else 'dnf install -y'
|
|
||||||
}}
|
|
||||||
|
|
||||||
# Rule catalog: control -> CIS level + whether a task implements it.
|
|
||||||
# `default` enables only implemented rules; `l1`/`l2` add the level-tagged ones.
|
|
||||||
cis_rule_catalog:
|
|
||||||
module_blacklist: {level: l1, implemented: true} # fs/net modprobe blacklist (list per profile)
|
|
||||||
usb_lockdown: {level: l2, implemented: true} # udev authorized_default=0 (aggressive)
|
|
||||||
sysctl_hardening: {level: l1, implemented: true}
|
|
||||||
ipv6_disable: {level: l2, implemented: true} # disable_ipv6 subset of the sysctl set
|
|
||||||
umask_default: {level: l1, implemented: true}
|
|
||||||
empty_password_login: {level: l1, implemented: true}
|
|
||||||
pwquality: {level: l1, implemented: true}
|
|
||||||
core_dumps: {level: l1, implemented: true}
|
|
||||||
shell_timeout: {level: l1, implemented: true}
|
|
||||||
journald_persistent: {level: l1, implemented: true}
|
|
||||||
sudo_logfile: {level: l1, implemented: true}
|
|
||||||
su_restriction: {level: l1, implemented: true}
|
|
||||||
faillock: {level: l1, implemented: true}
|
|
||||||
password_history: {level: l1, implemented: true}
|
|
||||||
tcp_wrappers: {level: l1, implemented: true}
|
|
||||||
crypto_policy: {level: l1, implemented: true} # RedHat non-Fedora only
|
|
||||||
mask_services: {level: l1, implemented: true}
|
|
||||||
cron_at_access: {level: l1, implemented: true}
|
|
||||||
file_permissions: {level: l1, implemented: true}
|
|
||||||
sshd_hardening: {level: l1, implemented: true}
|
|
||||||
password_expiry: {level: l1, implemented: true} # login.defs aging policy
|
|
||||||
aide: {level: l1, implemented: true} # file-integrity db + daily check
|
|
||||||
warning_banners: {level: l1, implemented: true} # /etc/issue, issue.net, motd
|
|
||||||
auditd: {level: l2, implemented: true} # audit daemon + CIS rule set
|
|
||||||
grub_password: {level: l1, implemented: true} # opt-in only; needs params.grub_password_hash
|
|
||||||
|
|
||||||
# Rules not listed are off. A per-host system.features.cis.rules map overlays this.
|
|
||||||
cis_profiles:
|
|
||||||
# default = established house behaviour, kept byte-for-byte unchanged.
|
|
||||||
default:
|
|
||||||
module_blacklist: true
|
|
||||||
usb_lockdown: true
|
|
||||||
sysctl_hardening: true
|
|
||||||
ipv6_disable: true
|
|
||||||
umask_default: true
|
|
||||||
empty_password_login: true
|
|
||||||
pwquality: true
|
|
||||||
core_dumps: true
|
|
||||||
shell_timeout: true
|
|
||||||
journald_persistent: true
|
|
||||||
sudo_logfile: true
|
|
||||||
su_restriction: true
|
|
||||||
faillock: true
|
|
||||||
password_history: true
|
|
||||||
tcp_wrappers: true
|
|
||||||
crypto_policy: true
|
|
||||||
mask_services: true
|
|
||||||
cron_at_access: true
|
|
||||||
file_permissions: true
|
|
||||||
sshd_hardening: true
|
|
||||||
# l1 = clean CIS Level 1: drops the L2 extras (usb_lockdown, ipv6_disable).
|
|
||||||
l1:
|
|
||||||
module_blacklist: true
|
|
||||||
sysctl_hardening: true
|
|
||||||
umask_default: true
|
|
||||||
empty_password_login: true
|
|
||||||
pwquality: true
|
|
||||||
core_dumps: true
|
|
||||||
shell_timeout: true
|
|
||||||
journald_persistent: true
|
|
||||||
sudo_logfile: true
|
|
||||||
su_restriction: true
|
|
||||||
faillock: true
|
|
||||||
password_history: true
|
|
||||||
tcp_wrappers: true
|
|
||||||
crypto_policy: true
|
|
||||||
mask_services: true
|
|
||||||
cron_at_access: true
|
|
||||||
file_permissions: true
|
|
||||||
sshd_hardening: true
|
|
||||||
password_expiry: true
|
|
||||||
aide: true
|
|
||||||
warning_banners: true
|
|
||||||
# l2 = l1 plus the defence-in-depth Level 2 controls.
|
|
||||||
l2:
|
|
||||||
module_blacklist: true
|
|
||||||
usb_lockdown: true
|
|
||||||
sysctl_hardening: true
|
|
||||||
ipv6_disable: true
|
|
||||||
umask_default: true
|
|
||||||
empty_password_login: true
|
|
||||||
pwquality: true
|
|
||||||
core_dumps: true
|
|
||||||
shell_timeout: true
|
|
||||||
journald_persistent: true
|
|
||||||
sudo_logfile: true
|
|
||||||
su_restriction: true
|
|
||||||
faillock: true
|
|
||||||
password_history: true
|
|
||||||
tcp_wrappers: true
|
|
||||||
crypto_policy: true
|
|
||||||
mask_services: true
|
|
||||||
cron_at_access: true
|
|
||||||
file_permissions: true
|
|
||||||
sshd_hardening: true
|
|
||||||
password_expiry: true
|
|
||||||
aide: true
|
|
||||||
warning_banners: true
|
|
||||||
auditd: true
|
|
||||||
|
|
||||||
# Override per host via system.features.cis.params: dicts deep-merge,
|
|
||||||
# list-valued keys (e.g. sshd_options) replace wholesale.
|
|
||||||
cis_param_defaults:
|
|
||||||
modules_blacklist:
|
|
||||||
- freevxfs
|
|
||||||
- jffs2
|
|
||||||
- hfs
|
|
||||||
- hfsplus
|
|
||||||
- cramfs
|
|
||||||
- udf
|
|
||||||
- usb-storage
|
|
||||||
- dccp
|
|
||||||
- sctp
|
|
||||||
- rds
|
|
||||||
- tipc
|
|
||||||
- firewire-core
|
|
||||||
- firewire-sbp2
|
|
||||||
- thunderbolt
|
|
||||||
sysctl:
|
|
||||||
fs.suid_dumpable: 0
|
|
||||||
kernel.dmesg_restrict: 1
|
|
||||||
kernel.kptr_restrict: 2
|
|
||||||
kernel.perf_event_paranoid: 3
|
|
||||||
kernel.unprivileged_bpf_disabled: 1
|
|
||||||
kernel.yama.ptrace_scope: 2
|
|
||||||
kernel.randomize_va_space: 2
|
|
||||||
net.ipv4.ip_forward: 0
|
|
||||||
net.ipv4.tcp_syncookies: 1
|
|
||||||
net.ipv4.icmp_echo_ignore_broadcasts: 1
|
|
||||||
net.ipv4.icmp_ignore_bogus_error_responses: 1
|
|
||||||
net.ipv4.conf.all.log_martians: 1
|
|
||||||
net.ipv4.conf.all.rp_filter: 1
|
|
||||||
net.ipv4.conf.all.secure_redirects: 0
|
|
||||||
net.ipv4.conf.all.send_redirects: 0
|
|
||||||
net.ipv4.conf.all.accept_redirects: 0
|
|
||||||
net.ipv4.conf.all.accept_source_route: 0
|
|
||||||
net.ipv4.conf.all.arp_ignore: 1
|
|
||||||
net.ipv4.conf.all.arp_announce: 2
|
|
||||||
net.ipv4.conf.default.log_martians: 1
|
|
||||||
net.ipv4.conf.default.rp_filter: 1
|
|
||||||
net.ipv4.conf.default.secure_redirects: 0
|
|
||||||
net.ipv4.conf.default.send_redirects: 0
|
|
||||||
net.ipv4.conf.default.accept_redirects: 0
|
|
||||||
net.ipv6.conf.all.accept_redirects: 0
|
|
||||||
net.ipv6.conf.all.disable_ipv6: 1
|
|
||||||
net.ipv6.conf.default.accept_redirects: 0
|
|
||||||
net.ipv6.conf.default.disable_ipv6: 1
|
|
||||||
net.ipv6.conf.lo.disable_ipv6: 1
|
|
||||||
sshd_options:
|
|
||||||
- {option: LogLevel, value: VERBOSE}
|
|
||||||
- {option: LoginGraceTime, value: "60"}
|
|
||||||
- {option: PermitRootLogin, value: "no"}
|
|
||||||
- {option: StrictModes, value: "yes"}
|
|
||||||
- {option: MaxAuthTries, value: "4"}
|
|
||||||
- {option: MaxSessions, value: "10"}
|
|
||||||
- {option: MaxStartups, value: "10:30:60"}
|
|
||||||
- {option: PubkeyAuthentication, value: "yes"}
|
|
||||||
- {option: HostbasedAuthentication, value: "no"}
|
|
||||||
- {option: IgnoreRhosts, value: "yes"}
|
|
||||||
- {option: PasswordAuthentication, value: "no"}
|
|
||||||
- {option: PermitEmptyPasswords, value: "no"}
|
|
||||||
- {option: KerberosAuthentication, value: "no"}
|
|
||||||
- {option: GSSAPIAuthentication, value: "no"}
|
|
||||||
- {option: AllowAgentForwarding, value: "no"}
|
|
||||||
- {option: AllowTcpForwarding, value: "no"}
|
|
||||||
- {option: KbdInteractiveAuthentication, value: "no"}
|
|
||||||
- {option: GatewayPorts, value: "no"}
|
|
||||||
- {option: X11Forwarding, value: "no"}
|
|
||||||
- {option: PermitUserEnvironment, value: "no"}
|
|
||||||
- {option: ClientAliveInterval, value: "300"}
|
|
||||||
- {option: ClientAliveCountMax, value: "1"}
|
|
||||||
- {option: PermitTunnel, value: "no"}
|
|
||||||
- {option: Banner, value: /etc/issue.net}
|
|
||||||
pwquality_minlen: 14
|
|
||||||
# pwquality strict set (l1/l2 only, cis_strict): SSG cis_server_l1 values.
|
|
||||||
pwquality_difok: 2
|
|
||||||
pwquality_maxrepeat: 3
|
|
||||||
pwquality_maxsequence: 3
|
|
||||||
pwquality_minclass: 4
|
|
||||||
pwquality_dictcheck: 1
|
|
||||||
tmout: 900
|
|
||||||
umask: "077"
|
|
||||||
umask_profile: "027"
|
|
||||||
faillock_deny: 5
|
|
||||||
faillock_unlock_time: 900
|
|
||||||
password_remember: 5
|
|
||||||
# pwhistory remember (l1/l2 only, cis_strict): SSG wants 24 via pam_pwhistory.
|
|
||||||
pwhistory_remember: 24
|
|
||||||
# password_expiry (l1/l2): /etc/login.defs aging.
|
|
||||||
pass_max_days: 365
|
|
||||||
pass_min_days: 1
|
|
||||||
pass_warn_age: 7
|
|
||||||
# account_disable_post_pw_expiration (l1/l2): days after expiry to lock (SSG=45).
|
|
||||||
pass_inactive: 45
|
|
||||||
# aide (l1/l2): daily integrity-check schedule.
|
|
||||||
aide_cron_hour: "5"
|
|
||||||
aide_cron_minute: "0"
|
|
||||||
# warning_banners (l1/l2): login/MOTD text.
|
|
||||||
banner_text: "Authorized access only. All activity may be monitored and reported."
|
|
||||||
# grub_password (opt-in only): a grub2 pbkdf2 hash; empty unless opted in.
|
|
||||||
grub_password_hash: ""
|
|
||||||
# insecure_packages (l1/l2 only, cis_strict): legacy cleartext clients to remove.
|
|
||||||
insecure_packages:
|
|
||||||
- telnet
|
|
||||||
|
|
||||||
# Only the module blacklist differs by profile: l1 trims to the L1 filesystem
|
|
||||||
# modules; default/l2 keep the full list.
|
|
||||||
cis_profile_params:
|
|
||||||
default: {}
|
|
||||||
l1:
|
|
||||||
modules_blacklist:
|
|
||||||
- cramfs
|
|
||||||
- freevxfs
|
|
||||||
- jffs2
|
|
||||||
- hfs
|
|
||||||
- hfsplus
|
|
||||||
- udf
|
|
||||||
- usb-storage
|
|
||||||
l2: {}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
# Post-reboot verification
|
cleanup_libvirt_image_dir: >-
|
||||||
cleanup_verify_boot: true
|
{{ vm_path if vm_path is defined and vm_path | length > 0 else '/var/lib/libvirt/images' }}
|
||||||
cleanup_boot_timeout: 300
|
cleanup_libvirt_cloudinit_path: >-
|
||||||
cleanup_remove_on_failure: true
|
{{ [cleanup_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- name: Remove Archiso and cloud-init disks
|
- name: Remove Archiso and cloud-init disks
|
||||||
when: hypervisor_type == "libvirt"
|
when: hypervisor == "libvirt"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
block:
|
block:
|
||||||
@@ -14,6 +14,19 @@
|
|||||||
- name: Initialize cleaned VM XML
|
- name: Initialize cleaned VM XML
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Remove boot ISO device from VM XML (target match)
|
||||||
|
community.general.xml:
|
||||||
|
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
||||||
|
xpath: "/domain/devices/disk[target/@dev='sda']"
|
||||||
|
state: absent
|
||||||
|
register: cleanup_libvirt_xml_strip_boot
|
||||||
|
|
||||||
|
- name: Update cleaned VM XML after removing boot ISO
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove boot ISO device from VM XML (source match)
|
- name: Remove boot ISO device from VM XML (source match)
|
||||||
when: boot_iso is defined and boot_iso | length > 0
|
when: boot_iso is defined and boot_iso | length > 0
|
||||||
@@ -27,17 +40,19 @@
|
|||||||
when: boot_iso is defined and boot_iso | length > 0
|
when: boot_iso is defined and boot_iso | length > 0
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove boot ISO device from VM XML (target fallback)
|
- name: Remove cloud-init ISO device from VM XML (target match)
|
||||||
community.general.xml:
|
community.general.xml:
|
||||||
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
||||||
xpath: "/domain/devices/disk[target/@dev='sda']"
|
xpath: "/domain/devices/disk[target/@dev='sdb']"
|
||||||
state: absent
|
state: absent
|
||||||
register: cleanup_libvirt_xml_strip_boot
|
register: cleanup_libvirt_xml_strip_cloudinit
|
||||||
|
|
||||||
- name: Update cleaned VM XML after removing boot ISO
|
- name: Update cleaned VM XML after removing cloud-init ISO
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove cloud-init ISO device from VM XML (source match)
|
- name: Remove cloud-init ISO device from VM XML (source match)
|
||||||
community.general.xml:
|
community.general.xml:
|
||||||
@@ -49,17 +64,7 @@
|
|||||||
- name: Update cleaned VM XML after removing cloud-init ISO source match
|
- name: Update cleaned VM XML after removing cloud-init ISO source match
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
- name: Remove cloud-init ISO device from VM XML (target fallback)
|
|
||||||
community.general.xml:
|
|
||||||
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
|
||||||
xpath: "/domain/devices/disk[target/@dev='sdb']"
|
|
||||||
state: absent
|
|
||||||
register: cleanup_libvirt_xml_strip_cloudinit
|
|
||||||
|
|
||||||
- name: Update cleaned VM XML after removing cloud-init ISO
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit.xmlstring }}"
|
|
||||||
|
|
||||||
- name: Strip XML declaration for libvirt define
|
- name: Strip XML declaration for libvirt define
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -71,12 +76,7 @@
|
|||||||
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
|
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
|
||||||
| trim
|
| trim
|
||||||
}}
|
}}
|
||||||
|
changed_when: false
|
||||||
- name: Ensure boot device is set to hard disk in VM XML
|
|
||||||
when: "'<boot ' not in cleanup_libvirt_domain_xml_clean"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cleanup_libvirt_domain_xml_clean: >-
|
|
||||||
{{ cleanup_libvirt_domain_xml_clean | regex_replace('(</type>)', '\1\n <boot dev="hd"/>') }}
|
|
||||||
|
|
||||||
- name: Update VM definition without installer media
|
- name: Update VM definition without installer media
|
||||||
community.libvirt.virt:
|
community.libvirt.virt:
|
||||||
@@ -85,53 +85,20 @@
|
|||||||
|
|
||||||
- name: Remove cloud-init disk
|
- name: Remove cloud-init disk
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
path: "{{ cleanup_libvirt_cloudinit_path }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Ensure VM is powered off before restart
|
- name: Ensure VM is powered off before restart
|
||||||
community.libvirt.virt:
|
community.libvirt.virt:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
state: destroyed
|
state: destroyed
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Enroll Secure Boot keys in VM NVRAM
|
|
||||||
when:
|
|
||||||
- system_cfg.features.secure_boot.enabled | default(false) | bool
|
|
||||||
- os != 'archlinux'
|
|
||||||
block:
|
|
||||||
- name: Find VM NVRAM file path
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
virsh -c {{ libvirt_uri | default('qemu:///system') }} dumpxml {{ hostname }}
|
|
||||||
| grep -oP '<nvram[^>]*>\K[^<]+'
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _sb_nvram_path
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Enroll Secure Boot keys via virt-fw-vars
|
|
||||||
when: _sb_nvram_path.stdout | default('') | length > 0
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- virt-fw-vars
|
|
||||||
- --inplace
|
|
||||||
- "{{ _sb_nvram_path.stdout | trim }}"
|
|
||||||
- --enroll-redhat
|
|
||||||
- --secure-boot
|
|
||||||
register: _sb_enroll_result
|
|
||||||
changed_when: _sb_enroll_result.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Start the VM
|
- name: Start the VM
|
||||||
community.libvirt.virt:
|
community.libvirt.virt:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
state: running
|
state: running
|
||||||
|
|
||||||
# delegate_to inventory_hostname: overrides play-level localhost to run wait_for_connection against the VM
|
|
||||||
- name: Wait for VM to boot up
|
- name: Wait for VM to boot up
|
||||||
delegate_to: "{{ inventory_hostname }}"
|
delegate_to: "{{ inventory_hostname }}"
|
||||||
ansible.builtin.wait_for_connection:
|
ansible.builtin.wait_for_connection:
|
||||||
timeout: 300
|
timeout: 300
|
||||||
failed_when: false
|
|
||||||
changed_when: false
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
- name: Cleanup physical install
|
- name: Cleanup physical install
|
||||||
when: system_cfg.type == "physical"
|
when: install_type == "physical"
|
||||||
ansible.builtin.include_tasks: physical.yml
|
ansible.builtin.include_tasks: physical.yml
|
||||||
|
|
||||||
- name: Cleanup virtual install
|
- name: Cleanup virtual install
|
||||||
when: system_cfg.type == "virtual"
|
when: install_type == "virtual"
|
||||||
ansible.builtin.include_tasks: virtual.yml
|
ansible.builtin.include_tasks: virtual.yml
|
||||||
|
|||||||
@@ -1,35 +1,27 @@
|
|||||||
---
|
---
|
||||||
- name: Setup Cleanup
|
- name: Setup Cleanup
|
||||||
when: hypervisor_type == "proxmox"
|
when: hypervisor == "proxmox"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
module_defaults:
|
|
||||||
community.proxmox.proxmox_disk: "{{ _proxmox_auth }}"
|
|
||||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
|
||||||
block:
|
block:
|
||||||
- name: Cleanup Setup Disks
|
- name: Cleanup Setup Disks
|
||||||
community.proxmox.proxmox_disk:
|
community.proxmox.proxmox_disk:
|
||||||
|
api_host: "{{ hypervisor_url }}"
|
||||||
|
api_user: "{{ hypervisor_username }}"
|
||||||
|
api_password: "{{ hypervisor_password }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ vm_id }}"
|
||||||
disk: "{{ item }}"
|
disk: "{{ item }}"
|
||||||
state: absent
|
state: absent
|
||||||
loop: >-
|
loop:
|
||||||
{{
|
- ide0
|
||||||
['ide0', 'ide2']
|
- ide2
|
||||||
+ (['ide1'] if not (os == 'rhel' and system_cfg.content.source == 'dvd') else [])
|
|
||||||
}}
|
|
||||||
failed_when: false
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Ensure the installer environment is powered off
|
- name: Start the VM
|
||||||
community.proxmox.proxmox_kvm:
|
community.proxmox.proxmox_kvm:
|
||||||
vmid: "{{ system_cfg.id }}"
|
api_host: "{{ hypervisor_url }}"
|
||||||
state: stopped
|
api_user: "{{ hypervisor_username }}"
|
||||||
force: true
|
api_password: "{{ hypervisor_password }}"
|
||||||
no_log: true
|
node: "{{ hypervisor_node }}"
|
||||||
|
vmid: "{{ vm_id }}"
|
||||||
- name: Boot the installed OS
|
state: restarted
|
||||||
community.proxmox.proxmox_kvm:
|
|
||||||
vmid: "{{ system_cfg.id }}"
|
|
||||||
state: started
|
|
||||||
no_log: true
|
|
||||||
|
|||||||
@@ -2,16 +2,6 @@
|
|||||||
- name: Unmount Disks
|
- name: Unmount Disks
|
||||||
become: true
|
become: true
|
||||||
block:
|
block:
|
||||||
- name: Unmount the bootstrap package cache
|
|
||||||
ansible.posix.mount:
|
|
||||||
path: /mnt/var/cache
|
|
||||||
state: unmounted
|
|
||||||
|
|
||||||
- name: Remove the bootstrap package cache so it is not sealed into the image
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/.bootstrap-cache
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Disable Swap
|
- name: Disable Swap
|
||||||
ansible.builtin.command: swapoff -a
|
ansible.builtin.command: swapoff -a
|
||||||
register: cleanup_swapoff_result
|
register: cleanup_swapoff_result
|
||||||
|
|||||||
@@ -6,152 +6,10 @@
|
|||||||
ansible.builtin.include_tasks: shutdown.yml
|
ansible.builtin.include_tasks: shutdown.yml
|
||||||
|
|
||||||
- name: Cleanup hypervisor resources
|
- name: Cleanup hypervisor resources
|
||||||
ansible.builtin.include_tasks: "{{ hypervisor_type }}.yml"
|
ansible.builtin.include_tasks: proxmox.yml
|
||||||
|
|
||||||
- name: Determine post-reboot connectivity
|
- name: Cleanup vCenter resources
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.include_tasks: vmware.yml
|
||||||
cleanup_post_reboot_can_connect: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
post_reboot_can_connect
|
|
||||||
if post_reboot_can_connect is defined
|
|
||||||
else (
|
|
||||||
(ansible_connection | default('ssh')) != 'ssh'
|
|
||||||
or ((system_cfg.network.ip | default('') | string | length) > 0)
|
|
||||||
or (
|
|
||||||
system_cfg.type == 'physical'
|
|
||||||
and (ansible_host | default('') | string | length) > 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) | bool
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Check VM accessibility after reboot
|
- name: Cleanup libvirt resources
|
||||||
when:
|
ansible.builtin.include_tasks: libvirt.yml
|
||||||
- cleanup_verify_boot | bool
|
|
||||||
- system_cfg.type == "virtual"
|
|
||||||
- cleanup_post_reboot_can_connect | bool
|
|
||||||
block:
|
|
||||||
- name: Attempt to connect to VM
|
|
||||||
delegate_to: "{{ inventory_hostname }}"
|
|
||||||
ansible.builtin.wait_for_connection:
|
|
||||||
timeout: "{{ cleanup_boot_timeout }}"
|
|
||||||
register: cleanup_vm_connection_check
|
|
||||||
failed_when: false
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: VM failed to boot - initiate cleanup
|
|
||||||
when:
|
|
||||||
- cleanup_remove_on_failure | bool
|
|
||||||
- cleanup_vm_connection_check is defined
|
|
||||||
- cleanup_vm_connection_check.failed | bool
|
|
||||||
- virtualization_vm_created_in_run | default(false) | bool
|
|
||||||
block:
|
|
||||||
- name: VM boot failure detected - removing VM
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: |
|
|
||||||
VM {{ hostname }} failed to boot after provisioning.
|
|
||||||
This VM was created in the current playbook run and will be removed
|
|
||||||
to prevent orphaned resources.
|
|
||||||
|
|
||||||
- name: Remove failed libvirt VM
|
|
||||||
when: hypervisor_type == "libvirt"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
block:
|
|
||||||
- name: Destroy libvirt VM
|
|
||||||
community.libvirt.virt:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
state: destroyed
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Undefine libvirt VM
|
|
||||||
community.libvirt.virt:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
command: undefine
|
|
||||||
|
|
||||||
- name: Remove libvirt VM disks
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item.path }}"
|
|
||||||
state: absent
|
|
||||||
loop: "{{ virtualization_libvirt_disks | default([]) }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
|
|
||||||
- name: Remove libvirt cloud-init disk
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Remove failed Proxmox VM
|
|
||||||
when: hypervisor_type == "proxmox"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
module_defaults:
|
|
||||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
|
||||||
no_log: true
|
|
||||||
block:
|
|
||||||
- name: Stop Proxmox VM
|
|
||||||
community.proxmox.proxmox_kvm:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
vmid: "{{ system_cfg.id }}"
|
|
||||||
state: stopped
|
|
||||||
|
|
||||||
- name: Delete Proxmox VM
|
|
||||||
community.proxmox.proxmox_kvm:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
vmid: "{{ system_cfg.id }}"
|
|
||||||
state: absent
|
|
||||||
unprivileged: false
|
|
||||||
|
|
||||||
- name: Remove failed VMware VM
|
|
||||||
when: hypervisor_type == "vmware"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
module_defaults:
|
|
||||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
|
||||||
no_log: true
|
|
||||||
block:
|
|
||||||
- name: Power off VMware VM
|
|
||||||
community.vmware.vmware_guest:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
folder: "{{ system_cfg.path | default('/') }}"
|
|
||||||
state: poweredoff
|
|
||||||
|
|
||||||
- name: Delete VMware VM
|
|
||||||
community.vmware.vmware_guest:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
folder: "{{ system_cfg.path | default('/') }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Remove failed Xen VM
|
|
||||||
when: hypervisor_type == "xen"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
block:
|
|
||||||
- name: Destroy Xen VM if running
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- xl
|
|
||||||
- destroy
|
|
||||||
- "{{ hostname }}"
|
|
||||||
register: cleanup_xen_destroy
|
|
||||||
failed_when: false
|
|
||||||
changed_when: cleanup_xen_destroy.rc == 0
|
|
||||||
|
|
||||||
- name: Remove Xen VM disks
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item.path }}"
|
|
||||||
state: absent
|
|
||||||
loop: "{{ virtualization_xen_disks | default([]) }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
|
|
||||||
- name: Remove Xen VM config file
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "/tmp/xen-{{ hostname }}.cfg"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: VM cleanup completed
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: VM {{ hostname }} has been successfully removed due to boot failure.
|
|
||||||
|
|||||||
@@ -1,47 +1,40 @@
|
|||||||
---
|
---
|
||||||
- name: Clean vCenter VM
|
- name: Clean vCenter VM
|
||||||
when: hypervisor_type == "vmware"
|
when: hypervisor == "vmware"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
module_defaults:
|
|
||||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
|
||||||
vmware.vmware.vm_powerstate: "{{ _vmware_auth }}"
|
|
||||||
no_log: true
|
|
||||||
block:
|
block:
|
||||||
- name: Remove CD-ROM from VM in vCenter
|
- name: Remove CD-ROM from VM in vCenter
|
||||||
|
when: hypervisor == "vmware"
|
||||||
community.vmware.vmware_guest:
|
community.vmware.vmware_guest:
|
||||||
|
hostname: "{{ hypervisor_url }}"
|
||||||
|
username: "{{ hypervisor_username }}"
|
||||||
|
password: "{{ hypervisor_password }}"
|
||||||
|
validate_certs: false
|
||||||
|
datacenter: "{{ hypervisor_datacenter }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
cdrom: >-
|
cdrom:
|
||||||
{{
|
- controller_number: 0
|
||||||
[
|
unit_number: 0
|
||||||
{
|
controller_type: sata
|
||||||
'controller_number': 0,
|
type: iso
|
||||||
'unit_number': 0,
|
iso_path: "{{ boot_iso }}"
|
||||||
'controller_type': 'sata',
|
state: absent
|
||||||
'type': 'iso',
|
- controller_number: 0
|
||||||
'iso_path': boot_iso,
|
unit_number: 1
|
||||||
'state': 'absent'
|
controller_type: sata
|
||||||
}
|
type: iso
|
||||||
]
|
iso_path: "{{ rhel_iso if rhel_iso is defined and rhel_iso | length > 0 else omit }}"
|
||||||
+ (
|
state: absent
|
||||||
[
|
|
||||||
{
|
|
||||||
'controller_number': 0,
|
|
||||||
'unit_number': 1,
|
|
||||||
'controller_type': 'sata',
|
|
||||||
'type': 'iso',
|
|
||||||
'iso_path': rhel_iso,
|
|
||||||
'state': 'absent'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
if (rhel_iso is defined and rhel_iso | length > 0
|
|
||||||
and not (os == 'rhel' and system_cfg.content.source == 'dvd'))
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Start VM in vCenter
|
- name: Start VM in vCenter
|
||||||
|
when: hypervisor == "vmware"
|
||||||
vmware.vmware.vm_powerstate:
|
vmware.vmware.vm_powerstate:
|
||||||
|
hostname: "{{ hypervisor_url }}"
|
||||||
|
username: "{{ hypervisor_username }}"
|
||||||
|
password: "{{ hypervisor_password }}"
|
||||||
|
validate_certs: false
|
||||||
|
datacenter: "{{ hypervisor_datacenter }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
state: powered-on
|
state: powered-on
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Cleanup Xen installer media
|
|
||||||
when: hypervisor_type == "xen"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
vars:
|
|
||||||
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
|
||||||
block:
|
|
||||||
- name: Ensure Xen disk definitions exist
|
|
||||||
ansible.builtin.include_tasks: ../../virtualization/tasks/_xen_disks.yml
|
|
||||||
|
|
||||||
- name: Render Xen VM configuration without installer media
|
|
||||||
vars:
|
|
||||||
xen_installer_media_enabled: false
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: xen.cfg.j2
|
|
||||||
dest: /tmp/xen-{{ hostname }}.cfg
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Destroy Xen VM if running
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- xl
|
|
||||||
- destroy
|
|
||||||
- "{{ hostname }}"
|
|
||||||
register: cleanup_xen_destroy
|
|
||||||
failed_when: false
|
|
||||||
changed_when: cleanup_xen_destroy.rc == 0
|
|
||||||
|
|
||||||
- name: Start Xen VM without installer media
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- xl
|
|
||||||
- create
|
|
||||||
- /tmp/xen-{{ hostname }}.cfg
|
|
||||||
register: cleanup_xen_start_result
|
|
||||||
changed_when: cleanup_xen_start_result.rc == 0
|
|
||||||
|
|
||||||
- name: Remove temporary Xen configuration file
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /tmp/xen-{{ hostname }}.cfg
|
|
||||||
state: absent
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
# Network backend is detected per host from the target rootfs in network.yml;
|
|
||||||
# no static map needed.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
# Shared task: update BLS (Boot Loader Specification) entries with kernel cmdline.
|
|
||||||
# Expects variable: _bls_cmdline (the kernel command line string)
|
|
||||||
- name: Find BLS entries
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: /mnt/boot/loader/entries
|
|
||||||
patterns: "*.conf"
|
|
||||||
register: _bls_entries
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Update BLS options
|
|
||||||
when: _bls_entries.files | length > 0
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: "{{ item.path }}"
|
|
||||||
regexp: "^options "
|
|
||||||
line: "options {{ _bls_cmdline }}"
|
|
||||||
loop: "{{ _bls_entries.files }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
# Resolve platform-specific configuration for the target OS family.
|
|
||||||
# Sets _configuration_platform from configuration_platform_config[os_family].
|
|
||||||
- name: Resolve platform-specific configuration
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- os_family is defined
|
|
||||||
- os_family in configuration_platform_config
|
|
||||||
fail_msg: >-
|
|
||||||
Unsupported os_family '{{ os_family | default("undefined") }}'.
|
|
||||||
Extend configuration_platform_config in vars/main.yml.
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Set platform configuration
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_configuration_platform: "{{ configuration_platform_config[os_family] }}"
|
|
||||||
|
|
||||||
- name: Override EFI loader to shim for Secure Boot
|
|
||||||
when:
|
|
||||||
- system_cfg.features.secure_boot.enabled | bool
|
|
||||||
- _configuration_platform.efi_loader != 'shimx64.efi'
|
|
||||||
- os != 'archlinux'
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_configuration_platform: >-
|
|
||||||
{{ _configuration_platform | combine({'efi_loader': 'shimx64.efi'}) }}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Configure MOTD
|
|
||||||
when: system_cfg.features.banner.motd | bool
|
|
||||||
block:
|
|
||||||
- name: Create MOTD file
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
********************************************************************
|
|
||||||
* AUTHORIZED ACCESS ONLY. ALL ACTIVITIES ARE MONITORED AND LOGGED. *
|
|
||||||
********************************************************************
|
|
||||||
dest: /mnt/etc/motd
|
|
||||||
mode: "0644"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Remove other MOTD files
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: absent
|
|
||||||
loop:
|
|
||||||
- /mnt/etc/motd.d/99-motd
|
|
||||||
- /mnt/etc/motd.d/cockpit
|
|
||||||
- /mnt/etc/motd.d/insights-client
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Create login banner
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: "{{ item }}"
|
|
||||||
content: |
|
|
||||||
**************************************************************
|
|
||||||
* WARNING: Unauthorized access to this system is prohibited. *
|
|
||||||
* All activities are monitored and logged. *
|
|
||||||
* Disconnect immediately if you are not an authorized user. *
|
|
||||||
**************************************************************
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
loop:
|
|
||||||
- /mnt/etc/issue
|
|
||||||
- /mnt/etc/issue.net
|
|
||||||
|
|
||||||
- name: Configure sudo banner
|
|
||||||
when: system_cfg.features.banner.sudo | bool
|
|
||||||
block:
|
|
||||||
- name: Detect the target sudo implementation
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/sudo --version"
|
|
||||||
register: configuration_sudo_version
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
# sudo-rs (Ubuntu 25.10+) implements neither `lecture` nor `lecture_file`
|
|
||||||
# and warns on every sudo call when they are set. It prints its version banner
|
|
||||||
# to stderr, not stdout, so match against both streams.
|
|
||||||
- name: Configure the sudo lecture
|
|
||||||
when: "'sudo-rs' not in (configuration_sudo_version.stdout ~ configuration_sudo_version.stderr)"
|
|
||||||
block:
|
|
||||||
- name: Create sudo lecture file
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
I am Groot, and I know what I'm doing.
|
|
||||||
dest: /mnt/etc/sudo_lecture
|
|
||||||
mode: "0644"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Enable sudo lecture in sudoers
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/sudoers
|
|
||||||
line: "{{ item }}"
|
|
||||||
state: present
|
|
||||||
create: true
|
|
||||||
mode: "0440"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
validate: "/usr/sbin/visudo --check --file=%s"
|
|
||||||
loop:
|
|
||||||
- "Defaults lecture=always"
|
|
||||||
- "Defaults lecture_file=/etc/sudo_lecture"
|
|
||||||
@@ -1,119 +1,64 @@
|
|||||||
---
|
---
|
||||||
- name: Configure Bootloader
|
- name: Configure Bootloader
|
||||||
vars:
|
|
||||||
_efi_vendor: >-
|
|
||||||
{{
|
|
||||||
"redhat" if os == "rhel"
|
|
||||||
else ("ubuntu" if os in ["ubuntu", "ubuntu-lts"] else os)
|
|
||||||
}}
|
|
||||||
_efi_loader: "{{ _configuration_platform.efi_loader }}"
|
|
||||||
block:
|
block:
|
||||||
- name: Install GRUB EFI binary
|
- name: Install Bootloader
|
||||||
when: _configuration_platform.grub_install
|
vars:
|
||||||
ansible.builtin.command: >-
|
configuration_use_efibootmgr: "{{ is_rhel | bool }}"
|
||||||
{{ chroot_command }} /usr/sbin/grub-install --target=x86_64-efi
|
configuration_efi_dir: "{{ partitioning_efi_mountpoint }}"
|
||||||
--efi-directory={{ partitioning_efi_mountpoint }}
|
configuration_bootloader_id: >-
|
||||||
--bootloader-id={{ _efi_vendor }}
|
{{ "ubuntu" if os | lower in ["ubuntu", "ubuntu-lts"] else os }}
|
||||||
--no-nvram
|
configuration_efi_vendor: >-
|
||||||
|
{{ "redhat" if os | lower in ["rhel8", "rhel9", "rhel10"] else os | lower }}
|
||||||
|
configuration_efibootmgr_cmd: >-
|
||||||
|
/usr/sbin/efibootmgr -c -L '{{ os }}' -d "{{ install_drive }}" -p 1
|
||||||
|
-l '\efi\EFI\{{ configuration_efi_vendor }}\shimx64.efi'
|
||||||
|
configuration_grub_cmd: >-
|
||||||
|
/usr/sbin/grub-install --target=x86_64-efi
|
||||||
|
--efi-directory={{ configuration_efi_dir }}
|
||||||
|
--bootloader-id={{ configuration_bootloader_id }}
|
||||||
|
configuration_bootloader_cmd: >-
|
||||||
|
{{ configuration_efibootmgr_cmd if configuration_use_efibootmgr else configuration_grub_cmd }}
|
||||||
|
ansible.builtin.command: "arch-chroot /mnt {{ configuration_bootloader_cmd }}"
|
||||||
register: configuration_bootloader_result
|
register: configuration_bootloader_result
|
||||||
changed_when: configuration_bootloader_result.rc == 0
|
changed_when: configuration_bootloader_result.rc == 0
|
||||||
|
|
||||||
- name: Check existing EFI boot entries
|
|
||||||
ansible.builtin.command: efibootmgr
|
|
||||||
register: configuration_efi_entries
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Ensure EFI boot entry exists
|
|
||||||
when: ('* ' + _efi_vendor) not in configuration_efi_entries.stdout
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
efibootmgr -c
|
|
||||||
-L '{{ _efi_vendor }}'
|
|
||||||
-d '{{ install_drive }}'
|
|
||||||
-p 1
|
|
||||||
-l '\EFI\{{ _efi_vendor }}\{{ _efi_loader }}'
|
|
||||||
register: configuration_efi_entry_result
|
|
||||||
changed_when: configuration_efi_entry_result.rc == 0
|
|
||||||
|
|
||||||
- name: Set installed OS as first EFI boot entry
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
efibootmgr | grep -i '{{ _efi_vendor }}' | grep -oP 'Boot\K[0-9A-F]+' | head -1
|
|
||||||
| xargs -I{} efibootmgr -o {}
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _efi_bootorder_result
|
|
||||||
changed_when: _efi_bootorder_result.rc == 0
|
|
||||||
|
|
||||||
- name: Ensure lvm2 for non btrfs filesystems
|
- name: Ensure lvm2 for non btrfs filesystems
|
||||||
when: os == "archlinux" and system_cfg.filesystem != "btrfs"
|
when: os | lower == "archlinux" and filesystem != "btrfs"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/mkinitcpio.conf
|
path: /mnt/etc/mkinitcpio.conf
|
||||||
regexp: "^(HOOKS=.*block)(?!.*lvm2)(.*)"
|
regexp: "^(HOOKS=.*block)(?!.*lvm2)(.*)"
|
||||||
line: "\\1 lvm2\\2"
|
line: '\1 lvm2\2'
|
||||||
backrefs: true
|
backrefs: true
|
||||||
|
|
||||||
- name: Regenerate initramfs
|
- name: Regenerate initramfs
|
||||||
when: _configuration_platform.initramfs_cmd | length > 0
|
vars:
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ _configuration_platform.initramfs_cmd }}"
|
configuration_initramfs_cmd: >-
|
||||||
|
{{
|
||||||
|
'/usr/sbin/mkinitcpio -P'
|
||||||
|
if os | lower == "archlinux"
|
||||||
|
else (
|
||||||
|
'/usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin '
|
||||||
|
+ '/usr/sbin/update-initramfs -u -k all'
|
||||||
|
if is_debian | bool
|
||||||
|
else '/usr/bin/dracut --regenerate-all --force'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
ansible.builtin.command: "arch-chroot /mnt {{ configuration_initramfs_cmd }}"
|
||||||
register: configuration_initramfs_result
|
register: configuration_initramfs_result
|
||||||
changed_when: configuration_initramfs_result.rc == 0
|
changed_when: configuration_initramfs_result.rc == 0
|
||||||
|
|
||||||
- name: Generate grub config (RedHat)
|
- name: Generate grub config
|
||||||
when: os_family == 'RedHat'
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} /usr/sbin/{{ _configuration_platform.grub_mkconfig_prefix }}
|
|
||||||
-o /boot/grub2/grub.cfg
|
|
||||||
register: configuration_grub_result
|
|
||||||
changed_when: configuration_grub_result.rc == 0
|
|
||||||
|
|
||||||
- name: Fix btrfs BLS boot variable in grub config
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.filesystem == 'btrfs'
|
|
||||||
ansible.builtin.replace:
|
|
||||||
path: /mnt/boot/grub2/grub.cfg
|
|
||||||
regexp: 'search --no-floppy --fs-uuid --set=boot \S+'
|
|
||||||
replace: 'set boot=$root'
|
|
||||||
|
|
||||||
- name: Create EFI grub.cfg wrapper for RedHat
|
|
||||||
when: os_family == 'RedHat'
|
|
||||||
vars:
|
vars:
|
||||||
_grub2_path: >-
|
configuration_efi_vendor: >-
|
||||||
|
{{ "redhat" if os | lower in ["rhel8", "rhel9", "rhel10"] else os | lower }}
|
||||||
|
configuration_grub_cfg_cmd: >-
|
||||||
{{
|
{{
|
||||||
'/grub2'
|
'/usr/sbin/grub2-mkconfig -o '
|
||||||
if (partitioning_separate_boot | bool)
|
+ partitioning_efi_mountpoint
|
||||||
else ('/@/boot/grub2' if system_cfg.filesystem == 'btrfs' else '/boot/grub2')
|
+ '/EFI/' + configuration_efi_vendor + '/grub.cfg'
|
||||||
|
if is_rhel | bool
|
||||||
|
else '/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg'
|
||||||
}}
|
}}
|
||||||
ansible.builtin.shell:
|
ansible.builtin.command: "arch-chroot /mnt {{ configuration_grub_cfg_cmd }}"
|
||||||
cmd: |
|
|
||||||
set -o pipefail
|
|
||||||
uuid=$(grep -m1 'search.*--set=root' /mnt/boot/grub2/grub.cfg | grep -oP '[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}')
|
|
||||||
cat > /mnt{{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/grub.cfg <<GRUBEOF
|
|
||||||
search --no-floppy --fs-uuid --set=dev $uuid
|
|
||||||
set prefix=(\$dev){{ _grub2_path }}
|
|
||||||
export \$prefix
|
|
||||||
configfile \$prefix/grub.cfg
|
|
||||||
GRUBEOF
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _grub_wrapper_result
|
|
||||||
changed_when: _grub_wrapper_result.rc == 0
|
|
||||||
|
|
||||||
- name: Generate grub config (non-RedHat)
|
|
||||||
when: os_family != 'RedHat'
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg"
|
|
||||||
register: configuration_grub_result
|
register: configuration_grub_result
|
||||||
changed_when: configuration_grub_result.rc == 0
|
changed_when: configuration_grub_result.rc == 0
|
||||||
|
|
||||||
- name: Rebuild GRUB as standalone EFI for Secure Boot
|
|
||||||
when:
|
|
||||||
- system_cfg.features.secure_boot.enabled | default(false) | bool
|
|
||||||
- os == 'archlinux'
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} grub-mkstandalone
|
|
||||||
-d /usr/lib/grub/x86_64-efi
|
|
||||||
-O x86_64-efi
|
|
||||||
--disable-shim-lock
|
|
||||||
-o {{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/grubx64.efi
|
|
||||||
boot/grub/grub.cfg=/boot/grub/grub.cfg
|
|
||||||
register: _grub_standalone_result
|
|
||||||
changed_when: _grub_standalone_result.rc == 0
|
|
||||||
|
|||||||
@@ -1,58 +1,50 @@
|
|||||||
---
|
---
|
||||||
- name: Configure disk encryption
|
- name: Configure disk encryption
|
||||||
when: system_cfg.luks.enabled | bool
|
when: partitioning_luks_enabled | bool
|
||||||
no_log: true
|
|
||||||
vars:
|
vars:
|
||||||
configuration_luks_passphrase: >-
|
configuration_luks_passphrase_effective: >-
|
||||||
{{ system_cfg.luks.passphrase | string }}
|
{{ partitioning_luks_passphrase | string }}
|
||||||
block:
|
block:
|
||||||
- name: Set LUKS configuration facts
|
- name: Set LUKS configuration facts
|
||||||
vars:
|
vars:
|
||||||
_raw_pcrs: >-
|
configuration_luks_mapper_name_value: >-
|
||||||
|
{{ partitioning_luks_mapper_name }}
|
||||||
|
configuration_luks_device_value: "{{ partitioning_luks_device }}"
|
||||||
|
configuration_luks_tpm2_pcrs_raw: >-
|
||||||
|
{{ partitioning_luks_tpm2_pcrs }}
|
||||||
|
configuration_luks_tpm2_pcrs_effective_value: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
system_cfg.luks.tpm2.pcrs
|
configuration_luks_tpm2_pcrs_raw
|
||||||
if system_cfg.luks.tpm2.pcrs is string
|
if configuration_luks_tpm2_pcrs_raw is string
|
||||||
else (system_cfg.luks.tpm2.pcrs | map('string') | join('+'))
|
else (configuration_luks_tpm2_pcrs_raw | map('string') | join('+'))
|
||||||
)
|
)
|
||||||
| string
|
| string
|
||||||
| replace(',', '+')
|
| replace(',', '+')
|
||||||
| regex_replace('\\s+', '')
|
| regex_replace('\\s+', '')
|
||||||
| regex_replace('^\\+|\\+$', '')
|
| regex_replace('^\\+|\\+$', '')
|
||||||
}}
|
}}
|
||||||
_sb_pcr7_safe: >-
|
|
||||||
{{
|
|
||||||
system_cfg.features.secure_boot.enabled | bool
|
|
||||||
and system_cfg.type | default('virtual') != 'virtual'
|
|
||||||
}}
|
|
||||||
luks_tpm2_pcrs: >-
|
|
||||||
{{
|
|
||||||
_raw_pcrs
|
|
||||||
if _raw_pcrs | length > 0
|
|
||||||
else ('7' if (_sb_pcr7_safe | bool) else '')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
configuration_luks_mapper_name: "{{ system_cfg.luks.mapper }}"
|
configuration_luks_mapper_name: "{{ configuration_luks_mapper_name_value }}"
|
||||||
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
||||||
configuration_luks_device: "{{ partitioning_luks_device }}"
|
configuration_luks_device: "{{ configuration_luks_device_value }}"
|
||||||
configuration_luks_options: "{{ system_cfg.luks.options }}"
|
configuration_luks_options: >-
|
||||||
|
{{ partitioning_luks_options }}
|
||||||
configuration_luks_auto_method: >-
|
configuration_luks_auto_method: >-
|
||||||
{{
|
{{
|
||||||
(system_cfg.luks.auto | bool)
|
(partitioning_luks_auto_decrypt | bool)
|
||||||
| ternary(
|
| ternary(
|
||||||
system_cfg.luks.method,
|
partitioning_luks_auto_decrypt_method,
|
||||||
'manual'
|
'manual'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
|
configuration_luks_tpm2_device: >-
|
||||||
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
{{ partitioning_luks_tpm2_device }}
|
||||||
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
configuration_luks_tpm2_pcrs: "{{ configuration_luks_tpm2_pcrs_raw }}"
|
||||||
configuration_luks_tpm2_token_lib: >-
|
configuration_luks_tpm2_pcrs_effective: "{{ configuration_luks_tpm2_pcrs_effective_value }}"
|
||||||
{{
|
configuration_luks_keyfile_path: >-
|
||||||
'/usr/lib/x86_64-linux-gnu/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
|
/etc/cryptsetup-keys.d/{{ configuration_luks_mapper_name_value }}.key
|
||||||
if os_family == 'Debian'
|
changed_when: false
|
||||||
else '/usr/lib64/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Validate LUKS UUID is available
|
- name: Validate LUKS UUID is available
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
@@ -64,79 +56,68 @@
|
|||||||
when: configuration_luks_auto_method in ['tpm2', 'keyfile']
|
when: configuration_luks_auto_method in ['tpm2', 'keyfile']
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- configuration_luks_passphrase | length > 0
|
- configuration_luks_passphrase_effective | length > 0
|
||||||
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
|
fail_msg: luks_passphrase (or partitioning_luks_passphrase) must be set for LUKS auto-decrypt.
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Detect TPM2 unlock method
|
- name: Enroll TPM2 for LUKS
|
||||||
ansible.builtin.include_tasks: encryption/initramfs_detect.yml
|
when: configuration_luks_auto_method == 'tpm2'
|
||||||
|
|
||||||
- name: Enroll TPM2 via systemd-cryptenroll
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('systemd-cryptenroll') == 'systemd-cryptenroll'
|
|
||||||
ansible.builtin.include_tasks: encryption/tpm2.yml
|
ansible.builtin.include_tasks: encryption/tpm2.yml
|
||||||
|
|
||||||
- name: Configure LUKS keyfile auto-decrypt
|
- name: Configure LUKS keyfile auto-decrypt
|
||||||
when: configuration_luks_auto_method == 'keyfile'
|
when: configuration_luks_auto_method == 'keyfile'
|
||||||
ansible.builtin.include_tasks: encryption/keyfile.yml
|
ansible.builtin.include_tasks: encryption/keyfile.yml
|
||||||
|
|
||||||
- name: Record final LUKS auto-decrypt method
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
configuration_luks_final_method: "{{ configuration_luks_auto_method }}"
|
|
||||||
|
|
||||||
- name: Report LUKS auto-decrypt configuration
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "LUKS auto-decrypt method: {{ configuration_luks_final_method }}"
|
|
||||||
|
|
||||||
- name: Build LUKS parameters
|
- name: Build LUKS parameters
|
||||||
vars:
|
vars:
|
||||||
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
|
configuration_luks_keyfile_in_use_value: "{{ configuration_luks_auto_method == 'keyfile' }}"
|
||||||
luks_option_list: >-
|
configuration_luks_option_list_value: >-
|
||||||
{{
|
{{
|
||||||
(configuration_luks_options | trim).split(',')
|
(configuration_luks_options | trim).split(',')
|
||||||
if configuration_luks_options | trim | length > 0
|
if configuration_luks_options | trim | length > 0
|
||||||
else []
|
else []
|
||||||
}}
|
}}
|
||||||
luks_tpm2_option_list: >-
|
configuration_luks_tpm2_option_list_value: >-
|
||||||
{{
|
{{
|
||||||
(configuration_luks_auto_method == 'tpm2' and (_tpm2_method | default('systemd-cryptenroll')) == 'systemd-cryptenroll')
|
(configuration_luks_auto_method == 'tpm2')
|
||||||
| ternary(
|
| ternary(
|
||||||
['tpm2-device=' + configuration_luks_tpm2_device]
|
['tpm2-device=' + configuration_luks_tpm2_device]
|
||||||
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs_effective]
|
||||||
if configuration_luks_tpm2_pcrs | length > 0 else []),
|
if configuration_luks_tpm2_pcrs_effective | length > 0 else []),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
luks_crypttab_keyfile: "{{ configuration_luks_keyfile_path if luks_keyfile_in_use else 'none' }}"
|
configuration_luks_crypttab_keyfile_value: >-
|
||||||
luks_crypttab_options: >-
|
{{ configuration_luks_keyfile_path if configuration_luks_keyfile_in_use_value else 'none' }}
|
||||||
|
configuration_luks_crypttab_options_value: >-
|
||||||
{{
|
{{
|
||||||
(['luks'] + luks_option_list + luks_tpm2_option_list)
|
(['luks'] + configuration_luks_option_list_value + configuration_luks_tpm2_option_list_value)
|
||||||
| join(',')
|
| join(',')
|
||||||
}}
|
}}
|
||||||
luks_rd_options: "{{ (luks_option_list + luks_tpm2_option_list) | join(',') }}"
|
configuration_luks_rd_options_value: >-
|
||||||
luks_kernel_args: >-
|
{{ (configuration_luks_option_list_value + configuration_luks_tpm2_option_list_value) | join(',') }}
|
||||||
|
configuration_luks_kernel_args_value: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
['rd.luks.name=' + configuration_luks_uuid + '=' + configuration_luks_mapper_name]
|
['rd.luks.name=' + configuration_luks_uuid + '=' + configuration_luks_mapper_name]
|
||||||
+ (
|
+ (
|
||||||
['rd.luks.options=' + configuration_luks_uuid + '=' + luks_rd_options]
|
['rd.luks.options=' + configuration_luks_uuid + '=' + configuration_luks_rd_options_value]
|
||||||
if luks_rd_options | length > 0 else []
|
if configuration_luks_rd_options_value | length > 0 else []
|
||||||
)
|
)
|
||||||
+ (
|
+ (
|
||||||
['rd.luks.key=' + configuration_luks_uuid + '=' + configuration_luks_keyfile_path]
|
['rd.luks.key=' + configuration_luks_uuid + '=' + configuration_luks_keyfile_path]
|
||||||
if luks_keyfile_in_use else []
|
if configuration_luks_keyfile_in_use_value else []
|
||||||
)
|
)
|
||||||
) | join(' ')
|
) | join(' ')
|
||||||
}}
|
}}
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
configuration_luks_keyfile_in_use: "{{ luks_keyfile_in_use }}"
|
configuration_luks_keyfile_in_use: "{{ configuration_luks_keyfile_in_use_value }}"
|
||||||
configuration_luks_option_list: "{{ luks_option_list }}"
|
configuration_luks_option_list: "{{ configuration_luks_option_list_value }}"
|
||||||
configuration_luks_tpm2_option_list: "{{ luks_tpm2_option_list }}"
|
configuration_luks_tpm2_option_list: "{{ configuration_luks_tpm2_option_list_value }}"
|
||||||
configuration_luks_crypttab_keyfile: "{{ luks_crypttab_keyfile }}"
|
configuration_luks_crypttab_keyfile: "{{ configuration_luks_crypttab_keyfile_value }}"
|
||||||
configuration_luks_crypttab_options: "{{ luks_crypttab_options }}"
|
configuration_luks_crypttab_options: "{{ configuration_luks_crypttab_options_value }}"
|
||||||
configuration_luks_rd_options: "{{ luks_rd_options }}"
|
configuration_luks_rd_options: "{{ configuration_luks_rd_options_value }}"
|
||||||
configuration_luks_kernel_args: "{{ luks_kernel_args }}"
|
configuration_luks_kernel_args: "{{ configuration_luks_kernel_args_value }}"
|
||||||
|
|
||||||
- name: Remove LUKS keyfile if TPM2 auto-decrypt is active
|
- name: Remove LUKS keyfile if TPM2 auto-decrypt is active
|
||||||
when: configuration_luks_auto_method == 'tpm2'
|
when: configuration_luks_auto_method == 'tpm2'
|
||||||
@@ -144,16 +125,231 @@
|
|||||||
path: /mnt{{ configuration_luks_keyfile_path }}
|
path: /mnt{{ configuration_luks_keyfile_path }}
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Configure initramfs for LUKS
|
- name: Write crypttab entry
|
||||||
ansible.builtin.include_tasks: encryption/initramfs.yml
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/crypttab
|
||||||
|
regexp: "^{{ configuration_luks_mapper_name }}\\s"
|
||||||
|
line: >-
|
||||||
|
{{ configuration_luks_mapper_name }} UUID={{ configuration_luks_uuid }}
|
||||||
|
{{ configuration_luks_crypttab_keyfile }} {{ configuration_luks_crypttab_options }}
|
||||||
|
create: true
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
- name: Configure crypttab
|
- name: Ensure keyfile pattern for initramfs-tools
|
||||||
ansible.builtin.include_tasks: encryption/crypttab.yml
|
when:
|
||||||
|
- is_debian | bool
|
||||||
|
- configuration_luks_keyfile_in_use
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/cryptsetup-initramfs/conf-hook
|
||||||
|
regexp: '^KEYFILE_PATTERN='
|
||||||
|
line: 'KEYFILE_PATTERN=/etc/cryptsetup-keys.d/*.key'
|
||||||
|
create: true
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Configure mkinitcpio hooks for LUKS
|
||||||
|
when: os | lower == 'archlinux'
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/mkinitcpio.conf
|
||||||
|
regexp: '^HOOKS='
|
||||||
|
line: >-
|
||||||
|
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
|
||||||
|
block sd-encrypt lvm2 filesystems fsck)
|
||||||
|
|
||||||
|
- name: Read mkinitcpio configuration
|
||||||
|
when: os | lower == 'archlinux'
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/mkinitcpio.conf
|
||||||
|
register: configuration_mkinitcpio_slurp
|
||||||
|
|
||||||
|
- name: Build mkinitcpio FILES list
|
||||||
|
when: os | lower == 'archlinux'
|
||||||
|
vars:
|
||||||
|
configuration_mkinitcpio_files_list_value: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
configuration_mkinitcpio_slurp.content | b64decode
|
||||||
|
| regex_findall('^FILES=\\(([^)]*)\\)', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
).split()
|
||||||
|
}}
|
||||||
|
configuration_mkinitcpio_files_list_new_value: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(configuration_mkinitcpio_files_list_value + [configuration_luks_keyfile_path])
|
||||||
|
if configuration_luks_keyfile_in_use
|
||||||
|
else (
|
||||||
|
configuration_mkinitcpio_files_list_value
|
||||||
|
| reject('equalto', configuration_luks_keyfile_path)
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_mkinitcpio_files_list_new: "{{ configuration_mkinitcpio_files_list_new_value }}"
|
||||||
|
|
||||||
|
- name: Configure mkinitcpio FILES list
|
||||||
|
when: os | lower == 'archlinux'
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/mkinitcpio.conf
|
||||||
|
regexp: '^FILES='
|
||||||
|
line: >-
|
||||||
|
FILES=({{
|
||||||
|
configuration_mkinitcpio_files_list_new | join(' ')
|
||||||
|
}})
|
||||||
|
|
||||||
|
- name: Ensure dracut config directory exists
|
||||||
|
when: is_rhel | bool
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /mnt/etc/dracut.conf.d
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
- name: Configure dracut for LUKS
|
- name: Configure dracut for LUKS
|
||||||
when: _initramfs_generator | default('') == 'dracut'
|
when: is_rhel | bool
|
||||||
ansible.builtin.include_tasks: encryption/dracut.yml
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/dracut.conf.d/crypt.conf
|
||||||
|
content: |
|
||||||
|
add_dracutmodules+=" crypt "
|
||||||
|
{% if configuration_luks_keyfile_in_use %}
|
||||||
|
install_items+=" {{ configuration_luks_keyfile_path }} "
|
||||||
|
{% endif %}
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
- name: Configure GRUB for LUKS
|
- name: Read kernel cmdline defaults
|
||||||
when: _initramfs_generator | default('') != 'dracut'
|
when: is_rhel | bool
|
||||||
ansible.builtin.include_tasks: encryption/grub.yml
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/kernel/cmdline
|
||||||
|
register: configuration_kernel_cmdline_slurp
|
||||||
|
|
||||||
|
- name: Build kernel cmdline with LUKS args
|
||||||
|
when: is_rhel | bool
|
||||||
|
vars:
|
||||||
|
configuration_kernel_cmdline_current_value: >-
|
||||||
|
{{ configuration_kernel_cmdline_slurp.content | b64decode | trim }}
|
||||||
|
configuration_kernel_cmdline_list_value: >-
|
||||||
|
{{
|
||||||
|
configuration_kernel_cmdline_current_value.split()
|
||||||
|
if configuration_kernel_cmdline_current_value | length > 0 else []
|
||||||
|
}}
|
||||||
|
configuration_kernel_cmdline_filtered_value: >-
|
||||||
|
{{
|
||||||
|
configuration_kernel_cmdline_list_value
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
configuration_kernel_cmdline_new_value: >-
|
||||||
|
{{
|
||||||
|
(configuration_kernel_cmdline_filtered_value + configuration_luks_kernel_args.split())
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_kernel_cmdline_new: "{{ configuration_kernel_cmdline_new_value }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Write kernel cmdline with LUKS args
|
||||||
|
when: is_rhel | bool
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/kernel/cmdline
|
||||||
|
mode: "0644"
|
||||||
|
content: "{{ configuration_kernel_cmdline_new }}\n"
|
||||||
|
|
||||||
|
- name: Find BLS entries
|
||||||
|
when: is_rhel | bool
|
||||||
|
ansible.builtin.find:
|
||||||
|
paths: /mnt/boot/loader/entries
|
||||||
|
patterns: "*.conf"
|
||||||
|
register: configuration_kernel_bls_entries
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Update BLS options with LUKS args
|
||||||
|
when:
|
||||||
|
- is_rhel | bool
|
||||||
|
- configuration_kernel_bls_entries.files | length > 0
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
regexp: '^options '
|
||||||
|
line: "options {{ configuration_kernel_cmdline_new }}"
|
||||||
|
loop: "{{ configuration_kernel_bls_entries.files }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
|
||||||
|
- name: Read grub defaults
|
||||||
|
when: not is_rhel | bool
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/default/grub
|
||||||
|
register: configuration_grub_slurp
|
||||||
|
|
||||||
|
- name: Build grub command lines with LUKS args
|
||||||
|
when: not is_rhel | bool
|
||||||
|
vars:
|
||||||
|
configuration_grub_content_value: "{{ configuration_grub_slurp.content | b64decode }}"
|
||||||
|
configuration_grub_cmdline_linux_value: >-
|
||||||
|
{{
|
||||||
|
configuration_grub_content_value
|
||||||
|
| regex_findall('^GRUB_CMDLINE_LINUX=\"(.*)\"', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
configuration_grub_cmdline_default_value: >-
|
||||||
|
{{
|
||||||
|
configuration_grub_content_value
|
||||||
|
| regex_findall('^GRUB_CMDLINE_LINUX_DEFAULT=\"(.*)\"', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
configuration_grub_cmdline_linux_list_value: >-
|
||||||
|
{{
|
||||||
|
configuration_grub_cmdline_linux_value.split()
|
||||||
|
if configuration_grub_cmdline_linux_value | length > 0 else []
|
||||||
|
}}
|
||||||
|
configuration_grub_cmdline_default_list_value: >-
|
||||||
|
{{
|
||||||
|
configuration_grub_cmdline_default_value.split()
|
||||||
|
if configuration_grub_cmdline_default_value | length > 0 else []
|
||||||
|
}}
|
||||||
|
configuration_luks_kernel_args_list_value: "{{ configuration_luks_kernel_args.split() }}"
|
||||||
|
configuration_grub_cmdline_linux_new_value: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(
|
||||||
|
configuration_grub_cmdline_linux_list_value
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
+ configuration_luks_kernel_args_list_value
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
configuration_grub_cmdline_default_new_value: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(
|
||||||
|
configuration_grub_cmdline_default_list_value
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
+ configuration_luks_kernel_args_list_value
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_grub_content: "{{ configuration_grub_content_value }}"
|
||||||
|
configuration_grub_cmdline_linux: "{{ configuration_grub_cmdline_linux_value }}"
|
||||||
|
configuration_grub_cmdline_default: "{{ configuration_grub_cmdline_default_value }}"
|
||||||
|
configuration_grub_cmdline_linux_new: "{{ configuration_grub_cmdline_linux_new_value }}"
|
||||||
|
configuration_grub_cmdline_default_new: "{{ configuration_grub_cmdline_default_new_value }}"
|
||||||
|
|
||||||
|
- name: Update GRUB_CMDLINE_LINUX_DEFAULT for LUKS
|
||||||
|
when: not is_rhel | bool
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/default/grub
|
||||||
|
regexp: '^GRUB_CMDLINE_LINUX_DEFAULT='
|
||||||
|
line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ configuration_grub_cmdline_default_new }}"'
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Write crypttab entry
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/crypttab
|
|
||||||
regexp: "^{{ configuration_luks_mapper_name }}\\s"
|
|
||||||
line: >-
|
|
||||||
{{ configuration_luks_mapper_name }} UUID={{ configuration_luks_uuid }}
|
|
||||||
{{ configuration_luks_crypttab_keyfile }} {{ configuration_luks_crypttab_options }}
|
|
||||||
create: true
|
|
||||||
mode: "0600"
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure dracut config directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/dracut.conf.d
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Configure dracut for LUKS
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/dracut.conf.d/crypt.conf
|
|
||||||
content: |
|
|
||||||
add_dracutmodules+=" crypt systemd "
|
|
||||||
{% if configuration_luks_keyfile_in_use | default(false) %}
|
|
||||||
install_items+=" {{ configuration_luks_keyfile_path }} "
|
|
||||||
{% endif %}
|
|
||||||
{% if configuration_luks_auto_method == 'tpm2' %}
|
|
||||||
add_dracutmodules+=" tpm2-tss "
|
|
||||||
install_items+=" {{ configuration_luks_tpm2_token_lib | default('') }} "
|
|
||||||
{% endif %}
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Ensure kernel cmdline directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/kernel
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Read existing kernel cmdline
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /mnt/etc/kernel/cmdline
|
|
||||||
register: _kernel_cmdline_slurp
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Build kernel cmdline with LUKS args
|
|
||||||
vars:
|
|
||||||
_cmdline_current: >-
|
|
||||||
{{ (_kernel_cmdline_slurp.content | default('') | b64decode | default('')) | trim }}
|
|
||||||
_cmdline_list: >-
|
|
||||||
{{ _cmdline_current.split() if _cmdline_current | length > 0 else [] }}
|
|
||||||
_cmdline_filtered: >-
|
|
||||||
{{
|
|
||||||
_cmdline_list
|
|
||||||
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
|
||||||
| list
|
|
||||||
}}
|
|
||||||
_cmdline_new: >-
|
|
||||||
{{
|
|
||||||
(_cmdline_filtered + configuration_luks_kernel_args.split())
|
|
||||||
| unique
|
|
||||||
| join(' ')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_dracut_kernel_cmdline: "{{ _cmdline_new }}"
|
|
||||||
|
|
||||||
- name: Write kernel cmdline with LUKS args
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/kernel/cmdline
|
|
||||||
mode: "0644"
|
|
||||||
content: "{{ _dracut_kernel_cmdline }}\n"
|
|
||||||
|
|
||||||
- name: Update BLS entries with LUKS kernel cmdline
|
|
||||||
when: os_family == 'RedHat'
|
|
||||||
vars:
|
|
||||||
_bls_cmdline: "{{ _dracut_kernel_cmdline }}"
|
|
||||||
ansible.builtin.include_tasks: ../_bls_update.yml
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Read grub defaults
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /mnt/etc/default/grub
|
|
||||||
register: configuration_grub_slurp
|
|
||||||
|
|
||||||
- name: Build grub command lines with LUKS args
|
|
||||||
vars:
|
|
||||||
grub_content: "{{ configuration_grub_slurp.content | b64decode }}"
|
|
||||||
grub_cmdline_linux: >-
|
|
||||||
{{
|
|
||||||
grub_content
|
|
||||||
| regex_findall('^GRUB_CMDLINE_LINUX=\"(.*)\"', multiline=True)
|
|
||||||
| default([])
|
|
||||||
| first
|
|
||||||
| default('')
|
|
||||||
}}
|
|
||||||
grub_cmdline_default: >-
|
|
||||||
{{
|
|
||||||
grub_content
|
|
||||||
| regex_findall('^GRUB_CMDLINE_LINUX_DEFAULT=\"(.*)\"', multiline=True)
|
|
||||||
| default([])
|
|
||||||
| first
|
|
||||||
| default('')
|
|
||||||
}}
|
|
||||||
grub_cmdline_linux_list: >-
|
|
||||||
{{
|
|
||||||
grub_cmdline_linux.split()
|
|
||||||
if grub_cmdline_linux | length > 0 else []
|
|
||||||
}}
|
|
||||||
grub_cmdline_default_list: >-
|
|
||||||
{{
|
|
||||||
grub_cmdline_default.split()
|
|
||||||
if grub_cmdline_default | length > 0 else []
|
|
||||||
}}
|
|
||||||
luks_kernel_args_list: "{{ configuration_luks_kernel_args.split() }}"
|
|
||||||
grub_cmdline_linux_new: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(
|
|
||||||
grub_cmdline_linux_list
|
|
||||||
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
|
||||||
| list
|
|
||||||
)
|
|
||||||
+ luks_kernel_args_list
|
|
||||||
)
|
|
||||||
| unique
|
|
||||||
| join(' ')
|
|
||||||
}}
|
|
||||||
grub_cmdline_default_new: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(
|
|
||||||
grub_cmdline_default_list
|
|
||||||
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
|
||||||
| list
|
|
||||||
)
|
|
||||||
+ luks_kernel_args_list
|
|
||||||
)
|
|
||||||
| unique
|
|
||||||
| join(' ')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
configuration_grub_content: "{{ grub_content }}"
|
|
||||||
configuration_grub_cmdline_linux: "{{ grub_cmdline_linux }}"
|
|
||||||
configuration_grub_cmdline_default: "{{ grub_cmdline_default }}"
|
|
||||||
configuration_grub_cmdline_linux_new: "{{ grub_cmdline_linux_new }}"
|
|
||||||
configuration_grub_cmdline_default_new: "{{ grub_cmdline_default_new }}"
|
|
||||||
|
|
||||||
- name: Update GRUB_CMDLINE_LINUX_DEFAULT for LUKS
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/default/grub
|
|
||||||
regexp: "^GRUB_CMDLINE_LINUX_DEFAULT="
|
|
||||||
line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ configuration_grub_cmdline_default_new }}"'
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
---
|
|
||||||
# Initramfs configuration for LUKS auto-unlock.
|
|
||||||
# Runs AFTER Build LUKS parameters (so configuration_luks_keyfile_in_use is set).
|
|
||||||
# _initramfs_generator and _tpm2_method are set by initramfs_detect.yml.
|
|
||||||
|
|
||||||
# --- clevis: install and bind TPM2 ---
|
|
||||||
- name: Install clevis in target system
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
vars:
|
|
||||||
_clevis_install_cmd:
|
|
||||||
Debian: >-
|
|
||||||
{{ chroot_command }} apt install -y
|
|
||||||
clevis clevis-luks clevis-tpm2 clevis-initramfs tpm2-tools
|
|
||||||
RedHat: >-
|
|
||||||
{{ chroot_command }} dnf install -y
|
|
||||||
clevis clevis-luks clevis-systemd tpm2-tools
|
|
||||||
Archlinux: >-
|
|
||||||
{{ chroot_command }} pacman -S --noconfirm --needed
|
|
||||||
clevis tpm2-tools
|
|
||||||
ansible.builtin.command: "{{ _clevis_install_cmd[os_family] }}"
|
|
||||||
register: _clevis_install_result
|
|
||||||
changed_when: _clevis_install_result.rc == 0
|
|
||||||
|
|
||||||
- name: Install clevis on installer for LUKS binding
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
community.general.pacman:
|
|
||||||
name:
|
|
||||||
- clevis
|
|
||||||
- tpm2-tools
|
|
||||||
state: present
|
|
||||||
retries: 3
|
|
||||||
delay: 5
|
|
||||||
|
|
||||||
- name: Create clevis passphrase file
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/root/.luks-enroll-key
|
|
||||||
content: "{{ configuration_luks_passphrase }}"
|
|
||||||
mode: "0600"
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Ensure TPM device accessible for clevis
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
ansible.builtin.shell: >-
|
|
||||||
ls /mnt/dev/tpmrm0 2>/dev/null
|
|
||||||
|| (ls /dev/tpmrm0 && cp -a /dev/tpmrm0 /mnt/dev/tpmrm0)
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Bind LUKS to TPM2 via clevis
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
vars:
|
|
||||||
_clevis_config: >-
|
|
||||||
{{
|
|
||||||
'{"pcr_ids":"' + configuration_luks_tpm2_pcrs + '"}'
|
|
||||||
if configuration_luks_tpm2_pcrs | length > 0
|
|
||||||
else '{}'
|
|
||||||
}}
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
clevis luks bind -f -k /mnt/root/.luks-enroll-key
|
|
||||||
-d {{ configuration_luks_device }} tpm2 '{{ _clevis_config }}'
|
|
||||||
register: _clevis_bind_result
|
|
||||||
changed_when: _clevis_bind_result.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
# Initramfs regeneration is handled by the bootloader task which runs after
|
|
||||||
# encryption configuration. Clevis hooks are included automatically by
|
|
||||||
# update-initramfs when clevis-initramfs is installed.
|
|
||||||
|
|
||||||
- name: Remove clevis passphrase file
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/root/.luks-enroll-key
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Report clevis binding result
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('') == 'clevis'
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
{{ 'Clevis TPM2 binding succeeded' if (_clevis_bind_result.rc | default(1)) == 0
|
|
||||||
else 'Clevis TPM2 binding failed: ' + (_clevis_bind_result.stderr | default('unknown')) + '. System will require passphrase at boot.' }}
|
|
||||||
|
|
||||||
# --- initramfs-tools: keyfile support (non-TPM2) ---
|
|
||||||
- name: Configure initramfs-tools keyfile pattern
|
|
||||||
when:
|
|
||||||
- _initramfs_generator | default('') == 'initramfs-tools'
|
|
||||||
- configuration_luks_keyfile_in_use | default(false) | bool
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/cryptsetup-initramfs/conf-hook
|
|
||||||
regexp: "^KEYFILE_PATTERN="
|
|
||||||
line: "KEYFILE_PATTERN=/etc/cryptsetup-keys.d/*.key"
|
|
||||||
create: true
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
# --- mkinitcpio: systemd + sd-encrypt hooks ---
|
|
||||||
- name: Configure mkinitcpio hooks for LUKS
|
|
||||||
when: _initramfs_generator | default('') == 'mkinitcpio'
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/mkinitcpio.conf
|
|
||||||
regexp: "^HOOKS="
|
|
||||||
line: >-
|
|
||||||
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
|
|
||||||
block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck)
|
|
||||||
|
|
||||||
- name: Read mkinitcpio configuration
|
|
||||||
when: _initramfs_generator | default('') == 'mkinitcpio'
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /mnt/etc/mkinitcpio.conf
|
|
||||||
register: configuration_mkinitcpio_slurp
|
|
||||||
|
|
||||||
- name: Build mkinitcpio FILES list
|
|
||||||
when: _initramfs_generator | default('') == 'mkinitcpio'
|
|
||||||
vars:
|
|
||||||
mkinitcpio_files_list: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
configuration_mkinitcpio_slurp.content | b64decode
|
|
||||||
| regex_findall('^FILES=\\(([^)]*)\\)', multiline=True)
|
|
||||||
| default([])
|
|
||||||
| first
|
|
||||||
| default('')
|
|
||||||
).split()
|
|
||||||
}}
|
|
||||||
mkinitcpio_files_list_new: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(mkinitcpio_files_list + [configuration_luks_keyfile_path])
|
|
||||||
if (configuration_luks_keyfile_in_use | default(false))
|
|
||||||
else (
|
|
||||||
mkinitcpio_files_list
|
|
||||||
| reject('equalto', configuration_luks_keyfile_path)
|
|
||||||
| list
|
|
||||||
)
|
|
||||||
)
|
|
||||||
| unique
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
configuration_mkinitcpio_files_list_new: "{{ mkinitcpio_files_list_new }}"
|
|
||||||
|
|
||||||
- name: Configure mkinitcpio FILES list
|
|
||||||
when: _initramfs_generator | default('') == 'mkinitcpio'
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/mkinitcpio.conf
|
|
||||||
regexp: "^FILES="
|
|
||||||
line: >-
|
|
||||||
FILES=({{
|
|
||||||
configuration_mkinitcpio_files_list_new | join(' ')
|
|
||||||
}})
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
---
|
|
||||||
# Resolve initramfs generator and TPM2 unlock method.
|
|
||||||
# Sets _initramfs_generator and _tpm2_method facts.
|
|
||||||
#
|
|
||||||
# Generator detection: derived from the platform's initramfs_cmd
|
|
||||||
# (dracut -> dracut, mkinitcpio -> mkinitcpio, else -> initramfs-tools)
|
|
||||||
# TPM2 method: systemd-cryptenroll when generator supports tpm2-device,
|
|
||||||
# clevis fallback otherwise. Non-native dracut installed automatically.
|
|
||||||
|
|
||||||
- name: Resolve initramfs generator
|
|
||||||
vars:
|
|
||||||
_user_generator: "{{ system_cfg.features.initramfs.generator | default('') }}"
|
|
||||||
_native_generator: >-
|
|
||||||
{{
|
|
||||||
'dracut' if _configuration_platform.initramfs_cmd is search('dracut')
|
|
||||||
else ('mkinitcpio' if _configuration_platform.initramfs_cmd is search('mkinitcpio')
|
|
||||||
else 'initramfs-tools')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_initramfs_generator: >-
|
|
||||||
{{ _user_generator if _user_generator | length > 0 else _native_generator }}
|
|
||||||
_initramfs_native_generator: "{{ _native_generator }}"
|
|
||||||
|
|
||||||
# --- Install non-native dracut if overridden or needed ---
|
|
||||||
- name: Install dracut in chroot when not native
|
|
||||||
when:
|
|
||||||
- _initramfs_generator == 'dracut'
|
|
||||||
- _initramfs_native_generator != 'dracut'
|
|
||||||
ansible.builtin.shell: >-
|
|
||||||
{{ chroot_command }} sh -c '
|
|
||||||
command -v apt >/dev/null 2>&1 && apt install -y dracut ||
|
|
||||||
command -v pacman >/dev/null 2>&1 && pacman -S --noconfirm dracut ||
|
|
||||||
command -v dnf >/dev/null 2>&1 && dnf install -y dracut
|
|
||||||
'
|
|
||||||
register: _dracut_install_result
|
|
||||||
changed_when: _dracut_install_result.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Override initramfs command to dracut
|
|
||||||
when:
|
|
||||||
- _initramfs_generator == 'dracut'
|
|
||||||
- _initramfs_native_generator != 'dracut'
|
|
||||||
vars:
|
|
||||||
# Generate dracut initramfs with output name matching what GRUB expects:
|
|
||||||
# mkinitcpio native: /boot/initramfs-linux.img (Arch convention)
|
|
||||||
# initramfs-tools native: /boot/initrd.img-<kver> (Debian convention)
|
|
||||||
_dracut_cmd: >-
|
|
||||||
{{
|
|
||||||
'bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initramfs-linux.img $kver; done"'
|
|
||||||
if _initramfs_native_generator == 'mkinitcpio'
|
|
||||||
else 'bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initrd.img-$kver $kver; done"'
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_configuration_platform: >-
|
|
||||||
{{ _configuration_platform | combine({'initramfs_cmd': _dracut_cmd}) }}
|
|
||||||
|
|
||||||
# --- TPM2 method detection ---
|
|
||||||
- name: Probe dracut for TPM2 module support
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _initramfs_generator != 'mkinitcpio'
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} dracut --list-modules"
|
|
||||||
register: _dracut_modules_check
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Resolve TPM2 unlock method
|
|
||||||
when: configuration_luks_auto_method == 'tpm2'
|
|
||||||
vars:
|
|
||||||
# mkinitcpio sd-encrypt supports tpm2-device natively
|
|
||||||
# dracut with tpm2-tss module supports tpm2-device natively
|
|
||||||
# everything else needs clevis
|
|
||||||
_supports_tpm2_native: >-
|
|
||||||
{{
|
|
||||||
_initramfs_generator == 'mkinitcpio'
|
|
||||||
or ('tpm2-tss' in (_dracut_modules_check.stdout | default('')))
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_tpm2_method: "{{ 'systemd-cryptenroll' if _supports_tpm2_native | bool else 'clevis' }}"
|
|
||||||
|
|
||||||
# --- Auto-upgrade to dracut when tpm2-tss available but generator isn't dracut ---
|
|
||||||
- name: Switch to dracut for TPM2 support
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method == 'systemd-cryptenroll'
|
|
||||||
- _initramfs_generator not in ['dracut', 'mkinitcpio']
|
|
||||||
vars:
|
|
||||||
_dracut_cmd: >-
|
|
||||||
bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initrd.img-$kver $kver; done"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_initramfs_generator: dracut
|
|
||||||
_configuration_platform: >-
|
|
||||||
{{ _configuration_platform | combine({'initramfs_cmd': _dracut_cmd}) }}
|
|
||||||
|
|
||||||
- name: Report TPM2 configuration
|
|
||||||
when: configuration_luks_auto_method == 'tpm2'
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "TPM2 unlock: {{ _tpm2_method | default('none') }} | initramfs: {{ _initramfs_generator }}"
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
{{
|
{{
|
||||||
lookup(
|
lookup(
|
||||||
'community.general.random_string',
|
'community.general.random_string',
|
||||||
length=(system_cfg.luks.keysize | int),
|
length=(partitioning_luks_keyfile_size | int),
|
||||||
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
when: configuration_luks_keyfile_unlock_test.rc != 0
|
when: configuration_luks_keyfile_unlock_test.rc != 0
|
||||||
community.crypto.luks_device:
|
community.crypto.luks_device:
|
||||||
device: "{{ configuration_luks_device }}"
|
device: "{{ configuration_luks_device }}"
|
||||||
passphrase: "{{ configuration_luks_passphrase }}"
|
passphrase: "{{ configuration_luks_passphrase_effective }}"
|
||||||
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
||||||
register: configuration_luks_addkey_result
|
register: configuration_luks_addkey_result
|
||||||
failed_when: false
|
failed_when: false
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
{{
|
{{
|
||||||
lookup(
|
lookup(
|
||||||
'community.general.random_string',
|
'community.general.random_string',
|
||||||
length=(system_cfg.luks.keysize | int),
|
length=(partitioning_luks_keyfile_size | int),
|
||||||
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -84,8 +84,9 @@
|
|||||||
- name: Retry adding keyfile to LUKS header
|
- name: Retry adding keyfile to LUKS header
|
||||||
community.crypto.luks_device:
|
community.crypto.luks_device:
|
||||||
device: "{{ configuration_luks_device }}"
|
device: "{{ configuration_luks_device }}"
|
||||||
passphrase: "{{ configuration_luks_passphrase }}"
|
passphrase: "{{ configuration_luks_passphrase_effective }}"
|
||||||
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
||||||
|
register: configuration_luks_addkey_retry
|
||||||
failed_when: false
|
failed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
@@ -103,13 +104,6 @@
|
|||||||
failed_when: false
|
failed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Warn about keyfile enrollment failure
|
|
||||||
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
LUKS keyfile enrollment failed - falling back to manual unlock at boot.
|
|
||||||
The system will prompt for the LUKS passphrase during startup.
|
|
||||||
|
|
||||||
- name: Fallback to manual LUKS unlock if keyfile enrollment failed
|
- name: Fallback to manual LUKS unlock if keyfile enrollment failed
|
||||||
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|||||||
@@ -1,35 +1,25 @@
|
|||||||
---
|
---
|
||||||
# TPM2 enrollment via systemd-cryptenroll.
|
|
||||||
# Works with dracut and mkinitcpio (sd-encrypt). The user-set passphrase
|
|
||||||
# remains as a backup unlock method - no auto-generated keyfiles.
|
|
||||||
- name: Enroll TPM2 for LUKS
|
- name: Enroll TPM2 for LUKS
|
||||||
block:
|
block:
|
||||||
- name: Create temporary passphrase file for TPM2 enrollment
|
- name: Create temporary passphrase file for TPM2 enrollment
|
||||||
ansible.builtin.tempfile:
|
ansible.builtin.tempfile:
|
||||||
path: /mnt/root
|
path: /mnt/tmp
|
||||||
prefix: luks-passphrase-
|
prefix: luks-passphrase-
|
||||||
state: file
|
state: file
|
||||||
register: _tpm2_passphrase_tempfile
|
register: configuration_luks_tpm2_passphrase_tempfile
|
||||||
|
|
||||||
- name: Write passphrase into temporary file
|
- name: Write passphrase into temporary file for TPM2 enrollment
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: "{{ _tpm2_passphrase_tempfile.path }}"
|
dest: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
|
||||||
content: "{{ configuration_luks_passphrase }}"
|
content: "{{ configuration_luks_passphrase_effective }}"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Ensure TPM device is accessible in chroot
|
- name: Enroll TPM2 token
|
||||||
ansible.builtin.shell: >-
|
|
||||||
ls /mnt/dev/tpmrm0 2>/dev/null
|
|
||||||
|| (ls /dev/tpmrm0 && cp -a /dev/tpmrm0 /mnt/dev/tpmrm0)
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Enroll TPM2 token via systemd-cryptenroll
|
|
||||||
vars:
|
vars:
|
||||||
_enroll_args: >-
|
configuration_luks_enroll_args: >-
|
||||||
{{
|
{{
|
||||||
[
|
[
|
||||||
'/usr/bin/systemd-cryptenroll',
|
'/usr/bin/systemd-cryptenroll',
|
||||||
@@ -37,28 +27,64 @@
|
|||||||
'--tpm2-with-pin=false',
|
'--tpm2-with-pin=false',
|
||||||
'--wipe-slot=tpm2',
|
'--wipe-slot=tpm2',
|
||||||
'--unlock-key-file=' + (
|
'--unlock-key-file=' + (
|
||||||
_tpm2_passphrase_tempfile.path | regex_replace('^/mnt', '')
|
configuration_luks_tpm2_passphrase_tempfile.path
|
||||||
|
| regex_replace('^/mnt', '')
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs_effective]
|
||||||
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
if configuration_luks_tpm2_pcrs_effective | length > 0 else [])
|
||||||
+ [configuration_luks_device]
|
+ [configuration_luks_device]
|
||||||
}}
|
}}
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ _enroll_args | join(' ') }}"
|
configuration_luks_enroll_chroot_args: "{{ ['arch-chroot', '/mnt'] + configuration_luks_enroll_args }}"
|
||||||
register: _tpm2_enroll_result
|
ansible.builtin.command:
|
||||||
changed_when: _tpm2_enroll_result.rc == 0
|
argv: "{{ configuration_luks_enroll_chroot_args }}"
|
||||||
|
register: configuration_luks_tpm2_enroll_chroot
|
||||||
|
changed_when: configuration_luks_tpm2_enroll_chroot.rc == 0
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Retry TPM2 enrollment in installer environment
|
||||||
|
when:
|
||||||
|
- (configuration_luks_tpm2_enroll_chroot.rc | default(1)) != 0
|
||||||
|
vars:
|
||||||
|
configuration_luks_enroll_args: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
'/usr/bin/systemd-cryptenroll',
|
||||||
|
'--tpm2-device=' + configuration_luks_tpm2_device,
|
||||||
|
'--tpm2-with-pin=false',
|
||||||
|
'--wipe-slot=tpm2',
|
||||||
|
'--unlock-key-file=' + configuration_luks_tpm2_passphrase_tempfile.path
|
||||||
|
]
|
||||||
|
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs_effective]
|
||||||
|
if configuration_luks_tpm2_pcrs_effective | length > 0 else [])
|
||||||
|
+ [configuration_luks_device]
|
||||||
|
}}
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ configuration_luks_enroll_args }}"
|
||||||
|
register: configuration_luks_tpm2_enroll_host
|
||||||
|
changed_when: configuration_luks_tpm2_enroll_host.rc == 0
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Validate TPM2 enrollment succeeded
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- >-
|
||||||
|
(configuration_luks_tpm2_enroll_chroot.rc | default(1)) == 0
|
||||||
|
or (configuration_luks_tpm2_enroll_host.rc | default(1)) == 0
|
||||||
|
fail_msg: >-
|
||||||
|
TPM2 enrollment failed.
|
||||||
|
chroot rc={{ configuration_luks_tpm2_enroll_chroot.rc | default('n/a') }},
|
||||||
|
host rc={{ configuration_luks_tpm2_enroll_host.rc | default('n/a') }},
|
||||||
|
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
|
||||||
|
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
|
||||||
rescue:
|
rescue:
|
||||||
- name: TPM2 enrollment failed
|
- name: Fallback to keyfile auto-decrypt
|
||||||
ansible.builtin.debug:
|
ansible.builtin.set_fact:
|
||||||
msg: >-
|
configuration_luks_auto_method: keyfile
|
||||||
TPM2 enrollment failed: {{ _tpm2_enroll_result.stderr | default('unknown') }}.
|
|
||||||
The system will require the passphrase for LUKS unlock on boot.
|
|
||||||
TPM2 can be enrolled post-deployment via: systemd-cryptenroll --tpm2-device=auto {{ configuration_luks_device }}
|
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Remove temporary passphrase file
|
- name: Remove TPM2 enrollment passphrase file
|
||||||
when: _tpm2_passphrase_tempfile.path is defined
|
when: configuration_luks_tpm2_passphrase_tempfile.path is defined
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ _tpm2_passphrase_tempfile.path }}"
|
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
changed_when: false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: Append vim configurations to vimrc
|
- name: Append vim configurations to vimrc
|
||||||
ansible.builtin.blockinfile:
|
ansible.builtin.blockinfile:
|
||||||
path: "{{ '/mnt/etc/vim/vimrc' if os_family == 'Debian' else '/mnt/etc/vimrc' }}"
|
path: "{{ '/mnt/etc/vim/vimrc' if is_debian | bool else '/mnt/etc/vimrc' }}"
|
||||||
block: |
|
block: |
|
||||||
set encoding=utf-8
|
set encoding=utf-8
|
||||||
set number
|
set number
|
||||||
@@ -9,11 +9,9 @@
|
|||||||
set smartindent
|
set smartindent
|
||||||
set mouse=a
|
set mouse=a
|
||||||
insertafter: EOF
|
insertafter: EOF
|
||||||
marker: "\" {mark} CUSTOM VIM CONFIG"
|
marker: ""
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
# Tuned for VM workloads: low swappiness, aggressive writeback, large page-cluster
|
|
||||||
# for zram. Override post-bootstrap via the linux role or sysctl if needed.
|
|
||||||
- name: Add memory tuning parameters
|
- name: Add memory tuning parameters
|
||||||
ansible.builtin.blockinfile:
|
ansible.builtin.blockinfile:
|
||||||
path: /mnt/etc/sysctl.d/90-memory.conf
|
path: /mnt/etc/sysctl.d/90-memory.conf
|
||||||
@@ -24,25 +22,48 @@
|
|||||||
vm.dirty_background_ratio=1
|
vm.dirty_background_ratio=1
|
||||||
vm.dirty_ratio=10
|
vm.dirty_ratio=10
|
||||||
vm.page-cluster=10
|
vm.page-cluster=10
|
||||||
marker: "# {mark} MEMORY TUNING"
|
marker: ""
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Create zram config
|
- name: Create zram config
|
||||||
when:
|
when: os | lower not in ['debian11', 'rhel8']
|
||||||
- (os != "debian" or (os_version | string) != "11") and os != "rhel"
|
|
||||||
- system_cfg.features.swap.enabled | bool
|
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /mnt/etc/systemd/zram-generator.conf
|
dest: /mnt/etc/systemd/zram-generator.conf
|
||||||
content: |
|
content: |
|
||||||
[zram0]
|
[zram0]
|
||||||
zram-size = ram / 2
|
zram-size = ram / 2
|
||||||
compression-algorithm = {{ 'zstd' if system_cfg.features.zstd.enabled | bool else 'lz4' }}
|
compression-algorithm = zstd
|
||||||
swap-priority = 100
|
swap-priority = 100
|
||||||
fs-type = swap
|
fs-type = swap
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Copy Custom Shell config
|
- name: Copy Custom Shell config
|
||||||
ansible.builtin.copy:
|
ansible.builtin.template:
|
||||||
src: custom.sh
|
src: custom.sh.j2
|
||||||
dest: /mnt/etc/profile.d/custom.sh
|
dest: /mnt/etc/profile.d/custom.sh
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Create login banner
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ item }}"
|
||||||
|
content: |
|
||||||
|
**************************************************************
|
||||||
|
* WARNING: Unauthorized access to this system is prohibited. *
|
||||||
|
* All activities are monitored and logged. *
|
||||||
|
* Disconnect immediately if you are not an authorized user. *
|
||||||
|
**************************************************************
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
loop:
|
||||||
|
- /mnt/etc/issue
|
||||||
|
- /mnt/etc/issue.net
|
||||||
|
|
||||||
|
- name: Remove motd files
|
||||||
|
when: os | lower in ["rhel8", "rhel9", "rhel10"]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- /mnt/etc/motd.d/cockpit
|
||||||
|
- /mnt/etc/motd.d/insights-client
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Enable the firewall daemon in the install chroot
|
|
||||||
when:
|
|
||||||
- firewall_phase == 'install'
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.firewall.enabled | bool
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ system_cfg.features.firewall.backend }}"
|
|
||||||
register: _firewall_enable
|
|
||||||
changed_when: _firewall_enable.rc == 0
|
|
||||||
failed_when: >-
|
|
||||||
_firewall_enable.rc != 0
|
|
||||||
and 'No such file or directory' not in (_firewall_enable.stderr | default(''))
|
|
||||||
and 'does not exist' not in (_firewall_enable.stderr | default(''))
|
|
||||||
|
|
||||||
# ufw's CLI needs a running kernel and is a no-op in the chroot (leaves ENABLED=no),
|
|
||||||
# so its activation and SSH rule are applied here, after reboot.
|
|
||||||
- name: Allow SSH through ufw before enabling
|
|
||||||
when:
|
|
||||||
- firewall_phase == 'postreboot'
|
|
||||||
- system_cfg.features.firewall.backend == 'ufw'
|
|
||||||
- system_cfg.features.firewall.enabled | bool
|
|
||||||
- system_cfg.features.ssh.enabled | bool
|
|
||||||
ansible.builtin.command: ufw allow 22/tcp
|
|
||||||
register: _ufw_allow
|
|
||||||
changed_when: "'added' in _ufw_allow.stdout or 'updated' in _ufw_allow.stdout"
|
|
||||||
|
|
||||||
- name: Activate ufw on the booted target
|
|
||||||
when:
|
|
||||||
- firewall_phase == 'postreboot'
|
|
||||||
- system_cfg.features.firewall.backend == 'ufw'
|
|
||||||
- system_cfg.features.firewall.enabled | bool
|
|
||||||
ansible.builtin.command: ufw --force enable
|
|
||||||
register: _ufw_enable
|
|
||||||
changed_when: "'active' in _ufw_enable.stdout"
|
|
||||||
@@ -16,44 +16,30 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Adjust XFS mount options and disable large extent
|
- name: Remove deprecated attr2 and disable large extent
|
||||||
when: os in ["almalinux", "rocky", "rhel"] and system_cfg.filesystem == "xfs"
|
when: os | lower in ["almalinux", "rhel8", "rhel9", "rhel10", "rocky"] and filesystem == "xfs"
|
||||||
ansible.builtin.replace:
|
ansible.builtin.replace:
|
||||||
path: /mnt/etc/fstab
|
path: /mnt/etc/fstab
|
||||||
regexp: "(xfs.*?)(attr2)"
|
regexp: "(xfs.*?)(attr2)"
|
||||||
replace: "\\1allocsize=64m"
|
replace: '\1allocsize=64m'
|
||||||
|
|
||||||
- name: Remove RHEL ISO fstab entry when not using local repo
|
|
||||||
when:
|
|
||||||
- os == "rhel"
|
|
||||||
- system_cfg.content.source != "dvd"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/fstab
|
|
||||||
regexp: "^.*\\/dvd.*$"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Replace ISO UUID entry with /dev/sr0 in fstab
|
- name: Replace ISO UUID entry with /dev/sr0 in fstab
|
||||||
when:
|
when: os in ["rhel8", "rhel9", "rhel10"]
|
||||||
- os == "rhel"
|
|
||||||
- system_cfg.content.source == "dvd"
|
|
||||||
vars:
|
vars:
|
||||||
configuration_fstab_dvd_line: >-
|
configuration_fstab_dvd_line: >-
|
||||||
{{
|
{{
|
||||||
'/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0'
|
'/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0'
|
||||||
if hypervisor_type == 'vmware'
|
if hypervisor == 'vmware'
|
||||||
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0'
|
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0'
|
||||||
}}
|
}}
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/fstab
|
path: /mnt/etc/fstab
|
||||||
regexp: "^.*\\/dvd.*$"
|
regexp: '^.*\/dvd.*$'
|
||||||
line: "{{ configuration_fstab_dvd_line }}"
|
line: "{{ configuration_fstab_dvd_line }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Write image from RHEL ISO to the target machine
|
- name: Write image from RHEL ISO to the target machine
|
||||||
when:
|
when: os in ["rhel8", "rhel9", "rhel10"] and hypervisor == 'vmware'
|
||||||
- os == "rhel"
|
|
||||||
- hypervisor_type == "vmware"
|
|
||||||
- system_cfg.content.source == "dvd"
|
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
argv:
|
argv:
|
||||||
- dd
|
- dd
|
||||||
@@ -71,10 +57,9 @@
|
|||||||
line: "{{ fstab_entry.line }}"
|
line: "{{ fstab_entry.line }}"
|
||||||
insertafter: EOF
|
insertafter: EOF
|
||||||
loop:
|
loop:
|
||||||
- { regexp: "^# TempFS$", line: "# TempFS" }
|
- {regexp: '^# TempFS$', line: '# TempFS'}
|
||||||
- { regexp: "^tmpfs\\s+/tmp\\s+", line: "tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0" }
|
- {regexp: '^tmpfs\\s+/tmp\\s+', line: 'tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0'}
|
||||||
- { regexp: "^tmpfs\\s+/var/tmp\\s+", line: "tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0" }
|
- {regexp: '^tmpfs\\s+/var/tmp\\s+', line: 'tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0'}
|
||||||
- { regexp: "^tmpfs\\s+/dev/shm\\s+", line: "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" }
|
- {regexp: '^tmpfs\\s+/dev/shm\\s+', line: 'tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0'}
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: fstab_entry
|
loop_var: fstab_entry
|
||||||
label: "{{ fstab_entry.regexp }}"
|
|
||||||
|
|||||||
@@ -1,69 +1,59 @@
|
|||||||
---
|
---
|
||||||
- name: Configure grub defaults
|
- name: Configure grub defaults
|
||||||
when: os_family != 'RedHat'
|
when: not is_rhel | bool
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /mnt/etc/default/grub
|
dest: /mnt/etc/default/grub
|
||||||
regexp: "{{ item.regexp }}"
|
regexp: "{{ item.regexp }}"
|
||||||
line: "{{ item.line }}"
|
line: "{{ item.line }}"
|
||||||
loop:
|
loop:
|
||||||
- regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
|
- regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
|
||||||
line: 'GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3{{ (" " ~ (_hardware_profile_kernel_params | join(" "))) if (_hardware_profile_kernel_params | default([]) | length > 0) else "" }}"'
|
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
|
||||||
- regexp: ^GRUB_TIMEOUT=
|
- regexp: ^GRUB_TIMEOUT=
|
||||||
line: GRUB_TIMEOUT=1
|
line: GRUB_TIMEOUT=1
|
||||||
loop_control:
|
|
||||||
label: "{{ item.line }}"
|
|
||||||
|
|
||||||
- name: Ensure grub defaults file exists for RHEL-based systems
|
- name: Ensure grub defaults file exists for RHEL-based systems
|
||||||
when: os_family == 'RedHat'
|
when: is_rhel | bool
|
||||||
block:
|
block:
|
||||||
- name: Build RHEL kernel command line defaults
|
- name: Build RHEL kernel command line defaults
|
||||||
vars:
|
vars:
|
||||||
grub_root_uuid: >-
|
configuration_grub_root_uuid_value: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
partitioning_main_uuid.stdout
|
partitioning_main_uuid.stdout
|
||||||
if system_cfg.filesystem == 'btrfs'
|
if (filesystem | lower) == 'btrfs'
|
||||||
else (partitioning_uuid_root | default([]) | first | default(''))
|
else (partitioning_uuid_root | default([]) | first | default(''))
|
||||||
)
|
)
|
||||||
| default('')
|
| default('')
|
||||||
| trim
|
| trim
|
||||||
}}
|
}}
|
||||||
grub_lvm_args: >-
|
configuration_grub_lvm_args_value: >-
|
||||||
{{
|
{{
|
||||||
(
|
['resume=/dev/mapper/sys-swap', 'rd.lvm.lv=sys/root', 'rd.lvm.lv=sys/swap']
|
||||||
['rd.lvm.lv=sys/root']
|
if (filesystem | lower) != 'btrfs'
|
||||||
+ (
|
|
||||||
['rd.lvm.lv=sys/swap', 'resume=/dev/mapper/sys-swap']
|
|
||||||
if system_cfg.features.swap.enabled | bool
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if system_cfg.filesystem != 'btrfs'
|
|
||||||
else []
|
else []
|
||||||
}}
|
}}
|
||||||
grub_root_flags: >-
|
configuration_grub_root_flags_value: >-
|
||||||
{{ ['rootflags=subvol=@'] if system_cfg.filesystem == 'btrfs' else [] }}
|
{{ ['rootflags=subvol=@'] if (filesystem | lower) == 'btrfs' else [] }}
|
||||||
# String-concat (not list-concat like grub_kernel_cmdline_base below): ansible-lint's
|
configuration_grub_cmdline_linux_base_value: >-
|
||||||
# jinja render trips on list+list when grub_lvm_args leads the expression here.
|
|
||||||
grub_cmdline_linux_base: >-
|
|
||||||
{{
|
{{
|
||||||
((grub_lvm_args | join(' ')) ~ ' ' ~ (_hardware_profile_kernel_params | default([]) | join(' '))) | trim
|
(['crashkernel=auto'] + configuration_grub_lvm_args_value)
|
||||||
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
grub_kernel_cmdline_base: >-
|
configuration_grub_kernel_cmdline_base_value: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
(['root=UUID=' + grub_root_uuid]
|
(['root=UUID=' + configuration_grub_root_uuid_value]
|
||||||
if grub_root_uuid | length > 0 else [])
|
if configuration_grub_root_uuid_value | length > 0 else [])
|
||||||
+ ['ro']
|
+ ['ro', 'crashkernel=auto']
|
||||||
+ grub_lvm_args
|
+ configuration_grub_lvm_args_value
|
||||||
+ grub_root_flags
|
+ configuration_grub_root_flags_value
|
||||||
+ (_hardware_profile_kernel_params | default([]))
|
|
||||||
)
|
)
|
||||||
| join(' ')
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
configuration_grub_cmdline_linux_base: "{{ grub_cmdline_linux_base }}"
|
configuration_grub_cmdline_linux_base: "{{ configuration_grub_cmdline_linux_base_value }}"
|
||||||
configuration_kernel_cmdline_base: "{{ grub_kernel_cmdline_base }}"
|
configuration_kernel_cmdline_base: "{{ configuration_grub_kernel_cmdline_base_value }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Check if grub defaults file exists
|
- name: Check if grub defaults file exists
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
@@ -98,14 +88,26 @@
|
|||||||
mode: "0644"
|
mode: "0644"
|
||||||
content: "{{ configuration_kernel_cmdline_base }}\n"
|
content: "{{ configuration_kernel_cmdline_base }}\n"
|
||||||
|
|
||||||
- name: Update BLS entries with kernel cmdline defaults
|
- name: Find BLS entries
|
||||||
vars:
|
ansible.builtin.find:
|
||||||
_bls_cmdline: "{{ configuration_kernel_cmdline_base }}"
|
paths: /mnt/boot/loader/entries
|
||||||
ansible.builtin.include_tasks: _bls_update.yml
|
patterns: "*.conf"
|
||||||
|
register: configuration_grub_bls_entries
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Update BLS options with kernel cmdline defaults
|
||||||
|
when: configuration_grub_bls_entries.files | length > 0
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
regexp: '^options '
|
||||||
|
line: "options {{ configuration_kernel_cmdline_base }}"
|
||||||
|
loop: "{{ configuration_grub_bls_entries.files }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
|
||||||
- name: Enable GRUB cryptodisk for encrypted /boot
|
- name: Enable GRUB cryptodisk for encrypted /boot
|
||||||
when: partitioning_grub_enable_cryptodisk | bool
|
when: partitioning_grub_enable_cryptodisk | bool
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/default/grub
|
path: /mnt/etc/default/grub
|
||||||
regexp: "^GRUB_ENABLE_CRYPTODISK="
|
regexp: '^GRUB_ENABLE_CRYPTODISK='
|
||||||
line: GRUB_ENABLE_CRYPTODISK=y
|
line: GRUB_ENABLE_CRYPTODISK=y
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
- name: Reload systemd in installer environment
|
- name: Reload systemd in installer environment
|
||||||
when: ansible_service_mgr == 'systemd'
|
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|
||||||
- name: Set local timezone
|
- name: Set local timezone
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
src: /usr/share/zoneinfo/{{ system_cfg.timezone }}
|
src: /usr/share/zoneinfo/Europe/Vienna
|
||||||
dest: /mnt/etc/localtime
|
dest: /mnt/etc/localtime
|
||||||
state: link
|
state: link
|
||||||
force: true
|
force: true
|
||||||
@@ -14,56 +13,54 @@
|
|||||||
- name: Setup locales
|
- name: Setup locales
|
||||||
block:
|
block:
|
||||||
- name: Configure locale.gen
|
- name: Configure locale.gen
|
||||||
when: _configuration_platform.locale_gen
|
when: not is_rhel | bool
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /mnt/etc/locale.gen
|
dest: /mnt/etc/locale.gen
|
||||||
regexp: "{{ item.regex }}"
|
regexp: "{{ item.regex }}"
|
||||||
line: "{{ item.line }}"
|
line: "{{ item.line }}"
|
||||||
loop:
|
loop:
|
||||||
- { regex: "{{ system_cfg.locale }} UTF-8", line: "{{ system_cfg.locale }} UTF-8" }
|
- {regex: en_US\.UTF-8 UTF-8, line: en_US.UTF-8 UTF-8}
|
||||||
loop_control:
|
|
||||||
label: "{{ item.line }}"
|
|
||||||
|
|
||||||
- name: Generate locales
|
- name: Generate locales
|
||||||
when: _configuration_platform.locale_gen
|
when: not is_rhel | bool
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/locale-gen"
|
ansible.builtin.command: arch-chroot /mnt /usr/sbin/locale-gen
|
||||||
register: configuration_locale_result
|
register: configuration_locale_result
|
||||||
changed_when: configuration_locale_result.rc == 0
|
changed_when: configuration_locale_result.rc == 0
|
||||||
|
|
||||||
- name: Compute hostname variables
|
- name: Set hostname
|
||||||
ansible.builtin.set_fact:
|
vars:
|
||||||
configuration_dns_domain: >-
|
|
||||||
{{ (system_cfg.network.dns.search | default([]) | first | default('')) | string }}
|
|
||||||
configuration_hostname_fqdn: >-
|
configuration_hostname_fqdn: >-
|
||||||
{{
|
{{
|
||||||
hostname
|
hostname
|
||||||
if '.' in hostname
|
if '.' in hostname
|
||||||
else (
|
else (
|
||||||
hostname + '.' + (system_cfg.network.dns.search | default([]) | first | default('') | string)
|
hostname + '.' + vm_dns_search
|
||||||
if (system_cfg.network.dns.search | default([]) | first | default('') | string) | length > 0
|
if vm_dns_search is defined and vm_dns_search | length
|
||||||
else hostname
|
else hostname
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
- name: Set hostname
|
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "{{ configuration_hostname_fqdn.split('.')[0] }}"
|
content: "{{ configuration_hostname_fqdn }}"
|
||||||
dest: /mnt/etc/hostname
|
dest: /mnt/etc/hostname
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Add host entry to /etc/hosts
|
- name: Add host entry to /etc/hosts
|
||||||
vars:
|
vars:
|
||||||
|
configuration_hostname_fqdn: >-
|
||||||
|
{{
|
||||||
|
hostname
|
||||||
|
if '.' in hostname
|
||||||
|
else (
|
||||||
|
hostname + '.' + vm_dns_search
|
||||||
|
if vm_dns_search is defined and vm_dns_search | length
|
||||||
|
else hostname
|
||||||
|
)
|
||||||
|
}}
|
||||||
configuration_hostname_short: "{{ hostname.split('.')[0] }}"
|
configuration_hostname_short: "{{ hostname.split('.')[0] }}"
|
||||||
configuration_hostname_entries: >-
|
configuration_hostname_entries: >-
|
||||||
{{ [configuration_hostname_fqdn, configuration_hostname_short] | unique | join(' ') }}
|
{{ [configuration_hostname_fqdn, configuration_hostname_short] | unique | join(' ') }}
|
||||||
configuration_hosts_ip: >-
|
|
||||||
{{
|
|
||||||
system_cfg.network.ip
|
|
||||||
if system_cfg.network.ip is defined and (system_cfg.network.ip | string | length) > 0
|
|
||||||
else inventory_hostname
|
|
||||||
}}
|
|
||||||
configuration_hosts_line: >-
|
configuration_hosts_line: >-
|
||||||
{{ configuration_hosts_ip }} {{ configuration_hostname_entries }}
|
{{ (vm_ip if vm_ip is defined and vm_ip | length > 0 else inventory_hostname) }} {{ configuration_hostname_entries }}
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/hosts
|
path: /mnt/etc/hosts
|
||||||
line: "{{ configuration_hosts_line }}"
|
line: "{{ configuration_hosts_line }}"
|
||||||
@@ -71,12 +68,24 @@
|
|||||||
|
|
||||||
- name: Create vconsole.conf
|
- name: Create vconsole.conf
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "KEYMAP={{ system_cfg.keymap }}"
|
content: KEYMAP=us
|
||||||
dest: /mnt/etc/vconsole.conf
|
dest: /mnt/etc/vconsole.conf
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Create locale.conf
|
- name: Create locale.conf
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "LANG={{ system_cfg.locale }}"
|
content: LANG=en_US.UTF-8
|
||||||
dest: /mnt/etc/locale.conf
|
dest: /mnt/etc/locale.conf
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Ensure SSH password authentication is enabled
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/ssh/sshd_config
|
||||||
|
regexp: "^#?PasswordAuthentication\\s+"
|
||||||
|
line: "PasswordAuthentication yes"
|
||||||
|
|
||||||
|
- name: SSH permit root login
|
||||||
|
ansible.builtin.replace:
|
||||||
|
path: /mnt/etc/ssh/sshd_config
|
||||||
|
regexp: "^#?PermitRootLogin.*"
|
||||||
|
replace: "PermitRootLogin yes"
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
---
|
---
|
||||||
- name: Resolve platform configuration
|
|
||||||
ansible.builtin.import_tasks: _resolve_platform.yml
|
|
||||||
|
|
||||||
- name: Include configuration tasks
|
- name: Include configuration tasks
|
||||||
when: configuration_task.when | default(true)
|
ansible.builtin.include_tasks: "{{ configuration_task }}"
|
||||||
ansible.builtin.include_tasks: "{{ configuration_task.file }}"
|
|
||||||
vars:
|
|
||||||
firewall_phase: install
|
|
||||||
loop:
|
loop:
|
||||||
- file: repositories.yml
|
- fstab.yml
|
||||||
- file: banner.yml
|
- locales.yml
|
||||||
- file: fstab.yml
|
- services.yml
|
||||||
- file: locales.yml
|
- grub.yml
|
||||||
- file: ssh.yml
|
- encryption.yml
|
||||||
- file: services.yml
|
- bootloader.yml
|
||||||
- file: firewall.yml
|
- extras.yml
|
||||||
- file: grub.yml
|
- network.yml
|
||||||
- file: encryption.yml
|
- users.yml
|
||||||
when: "{{ system_cfg.luks.enabled | bool }}"
|
- sudo.yml
|
||||||
- file: bootloader.yml
|
- selinux.yml
|
||||||
- file: secure_boot.yml
|
|
||||||
when: "{{ system_cfg.features.secure_boot.enabled | bool }}"
|
|
||||||
- file: extras.yml
|
|
||||||
- file: network.yml
|
|
||||||
- file: users.yml
|
|
||||||
- file: sudo.yml
|
|
||||||
- file: selinux.yml
|
|
||||||
when: "{{ os_family == 'RedHat' }}"
|
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: configuration_task
|
loop_var: configuration_task
|
||||||
label: "{{ configuration_task.file }}"
|
|
||||||
|
|||||||
@@ -1,51 +1,96 @@
|
|||||||
---
|
---
|
||||||
- name: Set DNS configuration facts
|
- name: Generate UUID for Network Profile
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
configuration_dns_list: "{{ system_cfg.network.dns.servers }}"
|
configuration_net_uuid: "{{ ('LAN-' ~ hostname) | ansible.builtin.to_uuid }}"
|
||||||
configuration_dns_search: "{{ system_cfg.network.dns.search }}"
|
changed_when: false
|
||||||
|
|
||||||
# 2+ unnamed interfaces would all match the first-ethernet glob, leaving the rest unconfigured.
|
- name: Read network interfaces
|
||||||
- name: Require an explicit name on every interface for multi-NIC
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- ip
|
||||||
|
- -o
|
||||||
|
- link
|
||||||
|
- show
|
||||||
|
register: configuration_ip_link
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Resolve network interface and MAC address
|
||||||
vars:
|
vars:
|
||||||
_unnamed: "{{ system_cfg.network.interfaces | map(attribute='name', default='') | map('string') | select('equalto', '') | list | length }}"
|
configuration_net_inf_from_facts: "{{ (ansible_default_ipv4 | default({})).get('interface', '') }}"
|
||||||
|
configuration_net_inf_from_ip: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
configuration_ip_link.stdout
|
||||||
|
| default('')
|
||||||
|
| regex_findall('^[0-9]+: ([^:]+):', multiline=True)
|
||||||
|
| reject('equalto', 'lo')
|
||||||
|
| list
|
||||||
|
| first
|
||||||
|
)
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
configuration_net_inf_effective: >-
|
||||||
|
{{ configuration_net_inf_from_facts | default(configuration_net_inf_from_ip, true) }}
|
||||||
|
configuration_net_inf_regex: "{{ configuration_net_inf_effective | ansible.builtin.regex_escape }}"
|
||||||
|
configuration_net_mac_from_virtualization: "{{ virtualization_mac_address | default('') }}"
|
||||||
|
configuration_net_mac_from_facts: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(ansible_facts | default({})).get(configuration_net_inf_effective, {}).get('macaddress', '')
|
||||||
|
)
|
||||||
|
| default(
|
||||||
|
(ansible_facts | default({})).get('ansible_' + configuration_net_inf_effective, {}).get('macaddress', ''),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
configuration_net_mac_from_ip: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
configuration_ip_link.stdout
|
||||||
|
| default('')
|
||||||
|
| regex_findall(
|
||||||
|
'^\\d+: ' ~ configuration_net_inf_regex ~ ':.*?link/ether\\s+([0-9A-Fa-f:]{17})',
|
||||||
|
multiline=True
|
||||||
|
)
|
||||||
|
| first
|
||||||
|
)
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_net_inf: "{{ configuration_net_inf_effective }}"
|
||||||
|
configuration_net_mac: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
configuration_net_mac_from_virtualization
|
||||||
|
| default(configuration_net_mac_from_facts, true)
|
||||||
|
| default(configuration_net_mac_from_ip, true)
|
||||||
|
)
|
||||||
|
| upper
|
||||||
|
}}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Validate Network Interface Name
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- system_cfg.network.interfaces | length <= 1 or _unnamed == 0
|
- configuration_net_inf | length > 0
|
||||||
fail_msg: >-
|
fail_msg: Failed to detect an active network interface.
|
||||||
Multi-NIC (system.network.interfaces with 2+ entries) requires a name on
|
|
||||||
every interface; the first-adapter glob only binds a single NIC.
|
|
||||||
|
|
||||||
# Probe /mnt to detect the stack the installed rootfs will run (nothing runs in
|
- name: Validate Network Interface MAC Address
|
||||||
# the chroot). NM is checked first and wins, since bootstrap installs it on every
|
ansible.builtin.assert:
|
||||||
# family; the rest are the fallback for a non-NM base image.
|
that:
|
||||||
- name: Probe the installed network stack on the target rootfs
|
- configuration_net_mac | length > 0
|
||||||
ansible.builtin.stat:
|
fail_msg: Failed to detect the MAC address for network interface {{ configuration_net_inf }}.
|
||||||
path: "{{ item }}"
|
|
||||||
register: configuration_net_probe
|
|
||||||
loop:
|
|
||||||
- /mnt/usr/bin/nmcli
|
|
||||||
- /mnt/usr/lib/systemd/system/NetworkManager.service
|
|
||||||
- /mnt/usr/sbin/netplan
|
|
||||||
- /mnt/etc/netplan
|
|
||||||
- /mnt/sbin/ifup
|
|
||||||
- /mnt/usr/sbin/ifup
|
|
||||||
- /mnt/etc/systemd/system/multi-user.target.wants/systemd-networkd.service
|
|
||||||
- /mnt/etc/systemd/system/dbus-org.freedesktop.network1.service
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
|
|
||||||
- name: Resolve the network backend from the probe
|
- name: Copy NetworkManager keyfile
|
||||||
vars:
|
ansible.builtin.template:
|
||||||
_found: "{{ configuration_net_probe.results | selectattr('stat.exists') | map(attribute='item') | list }}"
|
src: network.j2
|
||||||
ansible.builtin.set_fact:
|
dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection
|
||||||
configuration_network_backend: >-
|
mode: "0600"
|
||||||
{{
|
|
||||||
'nm' if (['/mnt/usr/bin/nmcli', '/mnt/usr/lib/systemd/system/NetworkManager.service'] | intersect(_found))
|
|
||||||
else 'netplan' if (['/mnt/usr/sbin/netplan', '/mnt/etc/netplan'] | intersect(_found))
|
|
||||||
else 'eni' if (['/mnt/sbin/ifup', '/mnt/usr/sbin/ifup'] | intersect(_found))
|
|
||||||
else 'networkd' if (['/mnt/etc/systemd/system/multi-user.target.wants/systemd-networkd.service', '/mnt/etc/systemd/system/dbus-org.freedesktop.network1.service'] | intersect(_found))
|
|
||||||
else 'nm'
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Configure networking for the detected backend {{ configuration_network_backend }}
|
- name: Fix Ubuntu unmanaged devices
|
||||||
ansible.builtin.include_tasks: "network_{{ configuration_network_backend }}.yml"
|
when: os | lower in ["ubuntu", "ubuntu-lts"]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
|
||||||
|
state: touch
|
||||||
|
mode: "0644"
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
# ifupdown can't glob iface stanzas (no mapping on ifupdown2/Proxmox), so ENI binds
|
|
||||||
# a literal name detected here. The chroot only sees live-ISO names: on a real
|
|
||||||
# ifupdown base, set system.network.interfaces[].name to the installed name. Bootstrap
|
|
||||||
# installs NetworkManager, so this fires only on a non-NM base image.
|
|
||||||
- name: Detect ethernet interface names
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- ip
|
|
||||||
- -o
|
|
||||||
- link
|
|
||||||
- show
|
|
||||||
register: configuration_eni_link
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Resolve detected ethernet interface names
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
configuration_eni_detected: >-
|
|
||||||
{{
|
|
||||||
configuration_eni_link.stdout | default('')
|
|
||||||
| regex_findall('^[0-9]+: ([^:@]+)[@:].*?link/ether', multiline=True)
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Ensure the network configuration directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/network
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Write the ifupdown interfaces file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: network_eni.j2
|
|
||||||
dest: /mnt/etc/network/interfaces
|
|
||||||
mode: "0644"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure the netplan directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/netplan
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Write the netplan configuration
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: network_netplan.j2
|
|
||||||
dest: /mnt/etc/netplan/10-sg.yaml
|
|
||||||
mode: "0600"
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure the systemd-networkd directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/systemd/network
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Write systemd-networkd configuration per interface
|
|
||||||
vars:
|
|
||||||
configuration_iface: "{{ item }}"
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: network_networkd.j2
|
|
||||||
dest: "/mnt/etc/systemd/network/10-static-{{ idx }}.network"
|
|
||||||
mode: "0644"
|
|
||||||
loop: "{{ system_cfg.network.interfaces }}"
|
|
||||||
loop_control:
|
|
||||||
index_var: idx
|
|
||||||
label: "10-static-{{ idx }}"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Copy NetworkManager keyfile per interface
|
|
||||||
vars:
|
|
||||||
configuration_iface: "{{ item }}"
|
|
||||||
configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: network.j2
|
|
||||||
dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
|
|
||||||
mode: "0600"
|
|
||||||
loop: "{{ system_cfg.network.interfaces }}"
|
|
||||||
loop_control:
|
|
||||||
index_var: idx
|
|
||||||
label: "LAN-{{ idx }}"
|
|
||||||
|
|
||||||
- name: Fix Ubuntu unmanaged devices
|
|
||||||
when: os in ["ubuntu", "ubuntu-lts"]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
|
|
||||||
state: touch
|
|
||||||
mode: "0644"
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
---
|
|
||||||
# Config runs against the chroot, so these write /mnt directly via templates
|
|
||||||
# rather than apt_repository/yum_repository, which would touch the live host.
|
|
||||||
- name: Write the apt sources.list
|
|
||||||
when: os_family == 'Debian'
|
|
||||||
vars:
|
|
||||||
_debian_release_map:
|
|
||||||
"12": bookworm
|
|
||||||
"13": trixie
|
|
||||||
unstable: sid
|
|
||||||
_ubuntu_release_map:
|
|
||||||
ubuntu: questing
|
|
||||||
ubuntu-lts: resolute
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: "{{ os | replace('-lts', '') }}.sources.list.j2"
|
|
||||||
dest: /mnt/etc/apt/sources.list
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Ensure apt performance and content-proxy configuration
|
|
||||||
when: os_family == 'Debian'
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
|
||||||
content: |
|
|
||||||
Acquire::Retries "3";
|
|
||||||
Acquire::http::Pipeline-Depth "10";
|
|
||||||
APT::Install-Recommends "false";
|
|
||||||
{% if system_cfg.content.proxy | length > 0 %}
|
|
||||||
Acquire::http::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
Acquire::https::Proxy "{{ system_cfg.content.proxy }}";
|
|
||||||
{% endif %}
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Drop the install-time DVD repo from the target on non-dvd sources
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.content.source != 'dvd'
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/yum.repos.d/redhat.repo
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Write the EL mirror repo on the target
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.content.source == 'mirror'
|
|
||||||
- system_cfg.content.url | length > 0
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: el_mirror.repo.j2
|
|
||||||
dest: "/mnt/etc/yum.repos.d/{{ os }}.repo"
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Find the stock vendor repos shipped by the release package
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.content.source == 'mirror'
|
|
||||||
- system_cfg.content.url | length > 0
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: /mnt/etc/yum.repos.d
|
|
||||||
patterns: "*.repo"
|
|
||||||
excludes: "{{ os }}.repo"
|
|
||||||
register: el_stock_repos
|
|
||||||
|
|
||||||
- name: Remove the stock vendor repos so only the custom mirror is reachable
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.content.source == 'mirror'
|
|
||||||
- system_cfg.content.url | length > 0
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item.path }}"
|
|
||||||
state: absent
|
|
||||||
loop: "{{ el_stock_repos.files | default([]) }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path }}"
|
|
||||||
|
|
||||||
- name: Configure the dnf content proxy on the target
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.content.proxy | length > 0
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/dnf/dnf.conf
|
|
||||||
line: "proxy={{ system_cfg.content.proxy }}"
|
|
||||||
regexp: "^proxy="
|
|
||||||
create: true
|
|
||||||
mode: "0644"
|
|
||||||
state: present
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
# Invoked post-reboot on the booted host, not in the chroot: subscription-manager
|
|
||||||
# needs a running systemd and the live network.
|
|
||||||
- name: Add the Satellite host to /etc/hosts
|
|
||||||
when: system_cfg.content.satellite.ip | length > 0
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/hosts
|
|
||||||
line: "{{ system_cfg.content.satellite.ip }} {{ system_cfg.content.satellite.host }}"
|
|
||||||
regexp: "[[:space:]]{{ system_cfg.content.satellite.host | regex_escape }}([[:space:]]|$)"
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Fetch the Katello CA consumer RPM
|
|
||||||
ansible.builtin.get_url:
|
|
||||||
url: >-
|
|
||||||
{{ system_cfg.content.satellite.ca_url
|
|
||||||
if (system_cfg.content.satellite.ca_url | length > 0)
|
|
||||||
else 'https://' ~ system_cfg.content.satellite.host ~ '/pub/katello-ca-consumer-latest.noarch.rpm' }}
|
|
||||||
dest: /tmp/katello-ca-consumer-latest.noarch.rpm
|
|
||||||
validate_certs: false
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Install the Katello CA consumer RPM
|
|
||||||
ansible.builtin.dnf:
|
|
||||||
name: /tmp/katello-ca-consumer-latest.noarch.rpm
|
|
||||||
state: present
|
|
||||||
disable_gpg_check: true
|
|
||||||
|
|
||||||
- name: Clean any stale subscription identity
|
|
||||||
ansible.builtin.command: subscription-manager clean
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Register with Satellite via activation key
|
|
||||||
no_log: true
|
|
||||||
community.general.redhat_subscription:
|
|
||||||
state: present
|
|
||||||
server_hostname: "{{ system_cfg.content.satellite.host }}"
|
|
||||||
org_id: "{{ system_cfg.content.satellite.org }}"
|
|
||||||
activationkey: "{{ system_cfg.content.satellite.activation_key }}"
|
|
||||||
environment: "{{ system_cfg.content.satellite.environment | default(omit, true) }}"
|
|
||||||
force_register: true
|
|
||||||
server_proxy_hostname: "{{ (system_cfg.content.proxy | urlsplit('hostname')) | default(omit, true) }}"
|
|
||||||
server_proxy_port: "{{ (system_cfg.content.proxy | urlsplit('port')) | default(omit, true) }}"
|
|
||||||
syspurpose:
|
|
||||||
service_level_agreement: "{{ system_cfg.content.satellite.service_level | default(omit, true) }}"
|
|
||||||
sync: true
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Validate Secure Boot is supported on this OS
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- os in ['archlinux', 'debian', 'ubuntu', 'ubuntu-lts',
|
|
||||||
'rhel', 'rocky', 'almalinux', 'fedora']
|
|
||||||
fail_msg: >-
|
|
||||||
Secure Boot is not supported on {{ os }} in this bootstrap. Supported:
|
|
||||||
Arch (sbctl) and Debian/Ubuntu/RHEL/Rocky/Alma/Fedora (shim). Disable
|
|
||||||
system.features.secure_boot.enabled or pick a supported OS.
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Configure shim-based Secure Boot
|
|
||||||
when: os != 'archlinux'
|
|
||||||
ansible.builtin.include_tasks: secure_boot/shim.yml
|
|
||||||
|
|
||||||
- name: Configure sbctl Secure Boot
|
|
||||||
when: os == 'archlinux'
|
|
||||||
ansible.builtin.include_tasks: secure_boot/sbctl.yml
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Configure sbctl Secure Boot
|
|
||||||
block:
|
|
||||||
- name: Create Secure Boot signing keys
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} sbctl create-keys"
|
|
||||||
register: _sbctl_create_keys
|
|
||||||
changed_when: _sbctl_create_keys.rc == 0
|
|
||||||
failed_when:
|
|
||||||
- _sbctl_create_keys.rc != 0
|
|
||||||
- "'already exists' not in (_sbctl_create_keys.stderr | default(''))"
|
|
||||||
|
|
||||||
- name: Enroll Secure Boot keys in firmware
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} sbctl enroll-keys --microsoft"
|
|
||||||
register: _sbctl_enroll
|
|
||||||
changed_when: _sbctl_enroll.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Install first-boot enrollment service if chroot enrollment failed
|
|
||||||
when: _sbctl_enroll.rc | default(1) != 0
|
|
||||||
block:
|
|
||||||
- name: Create first-boot sbctl enrollment service
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/systemd/system/sbctl-enroll.service
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Enroll Secure Boot keys via sbctl
|
|
||||||
ConditionPathExists=!/var/lib/sbctl/.enrolled
|
|
||||||
After=local-fs.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/sbctl enroll-keys --microsoft
|
|
||||||
ExecStartPost=/usr/bin/touch /var/lib/sbctl/.enrolled
|
|
||||||
RemainAfterExit=yes
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
- name: Enable first-boot enrollment service
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable sbctl-enroll.service"
|
|
||||||
register: _sbctl_service_enable
|
|
||||||
changed_when: _sbctl_service_enable.rc == 0
|
|
||||||
|
|
||||||
- name: Find kernel images to sign
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: /mnt/boot
|
|
||||||
patterns: "vmlinuz-*"
|
|
||||||
file_type: file
|
|
||||||
register: _sbctl_kernel_images
|
|
||||||
|
|
||||||
- name: Sign kernel images
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} sbctl sign -s {{ item.path | regex_replace('^/mnt', '') }}
|
|
||||||
loop: "{{ _sbctl_kernel_images.files }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.path | basename }}"
|
|
||||||
register: _sbctl_sign_kernel
|
|
||||||
changed_when: _sbctl_sign_kernel.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Sign GRUB EFI binary
|
|
||||||
vars:
|
|
||||||
_grub_efi_path: "{{ partitioning_efi_mountpoint }}/EFI/archlinux/grubx64.efi"
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} sbctl sign -s {{ _grub_efi_path }}
|
|
||||||
register: _sbctl_sign_grub
|
|
||||||
changed_when: _sbctl_sign_grub.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Ensure pacman hooks directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/pacman.d/hooks
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Install sbctl auto-signing pacman hook
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /mnt/etc/pacman.d/hooks/99-sbctl-sign.hook
|
|
||||||
mode: "0644"
|
|
||||||
content: |
|
|
||||||
[Trigger]
|
|
||||||
Operation = Install
|
|
||||||
Operation = Upgrade
|
|
||||||
Type = Path
|
|
||||||
Target = boot/vmlinuz-*
|
|
||||||
Target = usr/lib/modules/*/vmlinuz
|
|
||||||
|
|
||||||
[Action]
|
|
||||||
Description = Signing kernel images for Secure Boot...
|
|
||||||
When = PostTransaction
|
|
||||||
Exec = /usr/bin/sbctl sign-all
|
|
||||||
Depends = sbctl
|
|
||||||
|
|
||||||
- name: Verify sbctl signing status
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} sbctl verify"
|
|
||||||
register: _sbctl_verify
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Report sbctl Secure Boot status
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
Secure Boot (sbctl):
|
|
||||||
Enrollment={{ 'done' if (_sbctl_enroll.rc | default(1)) == 0 else 'deferred to first boot' }}.
|
|
||||||
{{ _sbctl_verify.stdout | default('Verify not available') }}
|
|
||||||
|
|
||||||
rescue:
|
|
||||||
- name: Secure Boot setup failed
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
sbctl Secure Boot setup failed.
|
|
||||||
On VMs make sure the OVMF firmware is in Setup Mode (fresh NVRAM).
|
|
||||||
On bare metal enter the firmware setup and switch to Setup Mode first.
|
|
||||||
To recover manually: sbctl create-keys && sbctl enroll-keys --microsoft && sbctl sign-all
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Configure shim-based Secure Boot
|
|
||||||
vars:
|
|
||||||
_efi_vendor: >-
|
|
||||||
{{
|
|
||||||
"redhat" if os == "rhel"
|
|
||||||
else ("ubuntu" if os in ["ubuntu", "ubuntu-lts"] else os)
|
|
||||||
}}
|
|
||||||
block:
|
|
||||||
- name: Find shim binary in target system
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
{{ chroot_command }} find /usr/lib/shim /boot/efi/EFI
|
|
||||||
\( -name 'shimx64.efi.signed.latest' -o -name 'shimx64.efi.dualsigned'
|
|
||||||
-o -name 'shimx64.efi.signed' -o -name 'shimx64.efi' \)
|
|
||||||
-type f | sort -r | head -1
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _shim_find_result
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Copy shim to EFI vendor directory
|
|
||||||
when:
|
|
||||||
- _shim_find_result.stdout | default('') | length > 0
|
|
||||||
- _configuration_platform.grub_install | bool
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
cp /mnt{{ _shim_find_result.stdout_lines | first }}
|
|
||||||
/mnt{{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/shimx64.efi
|
|
||||||
register: _shim_copy_result
|
|
||||||
changed_when: _shim_copy_result.rc == 0
|
|
||||||
|
|
||||||
- name: Verify shim is present
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/mnt{{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/shimx64.efi"
|
|
||||||
register: _shim_stat
|
|
||||||
|
|
||||||
- name: Report Secure Boot status
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: >-
|
|
||||||
Secure Boot (shim): {{
|
|
||||||
'shimx64.efi installed at ' ~ partitioning_efi_mountpoint ~ '/EFI/' ~ _efi_vendor
|
|
||||||
if (_shim_stat.stat.exists | default(false))
|
|
||||||
else 'shimx64.efi not found, shim package may handle placement on first boot'
|
|
||||||
}}
|
|
||||||
@@ -1,30 +1,18 @@
|
|||||||
---
|
---
|
||||||
- name: Fix SELinux
|
- name: Fix SELinux
|
||||||
when: os_family == 'RedHat'
|
when: is_rhel | bool
|
||||||
block:
|
block:
|
||||||
- name: Fix SELinux by pre-labeling the filesystem before first boot
|
- name: Fix SELinux by pre-labeling the filesystem before first boot
|
||||||
when: os in ['almalinux', 'rocky', 'rhel'] and system_cfg.features.selinux.enabled | bool
|
when: os | lower in ['almalinux', 'rhel8', 'rhel9', 'rhel10', 'rocky'] and selinux | bool
|
||||||
ansible.builtin.command: >
|
ansible.builtin.command: >
|
||||||
{{ chroot_command }} /sbin/setfiles -v -F
|
arch-chroot /mnt /sbin/setfiles -v -F
|
||||||
-e /dev -e /proc -e /sys -e /run
|
-e /dev -e /proc -e /sys -e /run
|
||||||
/etc/selinux/targeted/contexts/files/file_contexts /
|
/etc/selinux/targeted/contexts/files/file_contexts /
|
||||||
register: configuration_setfiles_result
|
register: configuration_setfiles_result
|
||||||
changed_when: configuration_setfiles_result.rc == 0
|
changed_when: configuration_setfiles_result.rc == 0
|
||||||
|
|
||||||
# setfiles in the chroot misses paths created at first boot (e.g. /var/lib/sss),
|
|
||||||
# leaving unlabeled_t files that block services under enforcing SELinux. Force a
|
|
||||||
# complete relabel on first boot; fixfiles consumes and removes the flag.
|
|
||||||
- name: Force a complete SELinux relabel on first boot
|
|
||||||
when: os in ['almalinux', 'rocky', 'rhel'] and system_cfg.features.selinux.enabled | bool
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/.autorelabel
|
|
||||||
state: touch
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
# Fedora: setfiles segfaults during bootstrap chroot relabeling, so SELinux
|
|
||||||
# is left permissive and expected to relabel on first boot.
|
|
||||||
- name: Disable SELinux
|
- name: Disable SELinux
|
||||||
when: os == "fedora" or not system_cfg.features.selinux.enabled | bool
|
when: os | lower == "fedora" or not selinux | bool
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/selinux/config
|
path: /mnt/etc/selinux/config
|
||||||
regexp: ^SELINUX=
|
regexp: ^SELINUX=
|
||||||
|
|||||||
@@ -1,248 +1,22 @@
|
|||||||
---
|
---
|
||||||
- name: Resolve desktop facts
|
- name: Enable Systemd Services
|
||||||
when: system_cfg.features.desktop.enabled | bool
|
ansible.builtin.command: >
|
||||||
vars:
|
arch-chroot /mnt systemctl enable NetworkManager
|
||||||
_autologin: "{{ system_cfg.features.desktop.autologin | default(false) }}"
|
{{ ' firewalld' if firewalld_enabled | bool else '' }}
|
||||||
ansible.builtin.set_fact:
|
{{
|
||||||
# KDE resolves to the plasmalogin unit on Arch/Fedora44+ (Plasma 6.6), else sddm.
|
' ssh' if os | lower in ['ubuntu', 'ubuntu-lts'] else
|
||||||
_desktop_dm: >-
|
(' sshd' if os | lower not in ['debian11', 'debian12', 'debian13'] else '')
|
||||||
{{
|
}}
|
||||||
('plasmalogin'
|
{{
|
||||||
if system_cfg.features.desktop.display_manager == 'plasma-login-manager'
|
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
|
||||||
else system_cfg.features.desktop.display_manager)
|
if os | lower == 'archlinux' else ''
|
||||||
if (system_cfg.features.desktop.display_manager | length > 0)
|
}}
|
||||||
else (
|
register: configuration_enable_services_result
|
||||||
('plasmalogin'
|
changed_when: configuration_enable_services_result.rc == 0
|
||||||
if (os == 'archlinux' or (os == 'fedora' and (os_version | int) >= 44))
|
|
||||||
else 'sddm')
|
|
||||||
if system_cfg.features.desktop.environment == 'kde'
|
|
||||||
else (configuration_desktop_dm_map[system_cfg.features.desktop.environment] | default(''))
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
_desktop_session: "{{ system_cfg.features.desktop.session | default('') }}"
|
|
||||||
# Explicit session wins, else the per-environment command. Single source of
|
|
||||||
# truth for the greetd assert, the config gate, and the template.
|
|
||||||
_greetd_session: >-
|
|
||||||
{{
|
|
||||||
system_cfg.features.desktop.session
|
|
||||||
if (system_cfg.features.desktop.session | default('') | length > 0)
|
|
||||||
else (configuration_desktop_session_cmd_map[system_cfg.features.desktop.environment] | default(''))
|
|
||||||
}}
|
|
||||||
_desktop_autologin_user: >-
|
|
||||||
{{
|
|
||||||
_autologin
|
|
||||||
if (_autologin | string | lower not in ['', 'false'] and _autologin in system_cfg.users)
|
|
||||||
else ''
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Enable systemd services
|
- name: Disable firewalld when disabled
|
||||||
when: _configuration_platform.init_system == 'systemd'
|
when: not firewalld_enabled | bool
|
||||||
vars:
|
ansible.builtin.command: arch-chroot /mnt systemctl disable --now firewalld
|
||||||
configuration_systemd_services: >-
|
register: configuration_disable_firewalld_result
|
||||||
{{
|
changed_when: configuration_disable_firewalld_result.rc == 0
|
||||||
['NetworkManager', _configuration_platform.time_sync_service]
|
failed_when: false
|
||||||
+ ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else [])
|
|
||||||
+ (['logrotate'] if os == 'archlinux' else [])
|
|
||||||
+ (['bluetooth'] if system_cfg.features.desktop.enabled | bool else [])
|
|
||||||
}}
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}"
|
|
||||||
loop: "{{ configuration_systemd_services }}"
|
|
||||||
register: configuration_enable_service_result
|
|
||||||
changed_when: configuration_enable_service_result.rc == 0
|
|
||||||
failed_when: >-
|
|
||||||
configuration_enable_service_result.rc != 0
|
|
||||||
and 'No such file or directory' not in (configuration_enable_service_result.stderr | default(''))
|
|
||||||
and 'does not exist' not in (configuration_enable_service_result.stderr | default(''))
|
|
||||||
|
|
||||||
- name: Check for the EL qemu-guest-agent RPC allow-list
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: /mnt/etc/sysconfig/qemu-ga
|
|
||||||
register: configuration_qga_sysconfig
|
|
||||||
|
|
||||||
- name: Allow clone-stamping RPCs in the EL qemu-guest-agent allow-list
|
|
||||||
when: configuration_qga_sysconfig.stat.exists
|
|
||||||
ansible.builtin.replace:
|
|
||||||
path: /mnt/etc/sysconfig/qemu-ga
|
|
||||||
regexp: '^(FILTER_RPC_ARGS="--allow-rpcs=(?:(?!guest-exec)[^"])*)"'
|
|
||||||
replace: '\1,guest-exec,guest-exec-status,guest-file-open,guest-file-close,guest-file-read,guest-file-write"'
|
|
||||||
|
|
||||||
- name: Enable display manager for selected desktop
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm | length > 0
|
|
||||||
- _desktop_dm != 'ly'
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ _desktop_dm }}"
|
|
||||||
register: configuration_enable_dm_result
|
|
||||||
changed_when: configuration_enable_dm_result.rc == 0
|
|
||||||
# Unlike optional services above, a missing/unenabled DM is fatal: chroot
|
|
||||||
# systemctl can exit 0 while only warning on stderr, so check both.
|
|
||||||
failed_when: >-
|
|
||||||
configuration_enable_dm_result.rc != 0
|
|
||||||
or 'No such file or directory' in (configuration_enable_dm_result.stderr | default(''))
|
|
||||||
or 'does not exist' in (configuration_enable_dm_result.stderr | default(''))
|
|
||||||
|
|
||||||
- name: Enable ly on its tty
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm == 'ly'
|
|
||||||
vars:
|
|
||||||
_ly_tty: tty2
|
|
||||||
block:
|
|
||||||
- name: Enable ly display manager
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl enable ly@{{ _ly_tty }}.service"
|
|
||||||
register: configuration_enable_ly_result
|
|
||||||
changed_when: configuration_enable_ly_result.rc == 0
|
|
||||||
failed_when: >-
|
|
||||||
configuration_enable_ly_result.rc != 0
|
|
||||||
or 'No such file or directory' in (configuration_enable_ly_result.stderr | default(''))
|
|
||||||
or 'does not exist' in (configuration_enable_ly_result.stderr | default(''))
|
|
||||||
|
|
||||||
# ly drives the VT itself; mask getty so logind never spawns a login on that tty.
|
|
||||||
- name: Mask getty on ly's tty
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl mask getty@{{ _ly_tty }}.service"
|
|
||||||
register: configuration_mask_getty_result
|
|
||||||
changed_when: configuration_mask_getty_result.rc == 0
|
|
||||||
failed_when: >-
|
|
||||||
configuration_mask_getty_result.rc != 0
|
|
||||||
and 'No such file or directory' not in (configuration_mask_getty_result.stderr | default(''))
|
|
||||||
and 'does not exist' not in (configuration_mask_getty_result.stderr | default(''))
|
|
||||||
|
|
||||||
- name: Set default systemd target
|
|
||||||
when: _configuration_platform.init_system == 'systemd'
|
|
||||||
vars:
|
|
||||||
_default_target: "{{ 'graphical.target' if system_cfg.features.desktop.enabled | bool else 'multi-user.target' }}"
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl set-default {{ _default_target }}"
|
|
||||||
register: _set_default_target_result
|
|
||||||
changed_when: _set_default_target_result.rc == 0
|
|
||||||
|
|
||||||
- name: Enable PipeWire user services globally
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} systemctl --global enable {{ item }}"
|
|
||||||
loop: "{{ configuration_desktop_audio_units }}"
|
|
||||||
register: _desktop_audio_result
|
|
||||||
changed_when: _desktop_audio_result.rc == 0
|
|
||||||
failed_when: >-
|
|
||||||
_desktop_audio_result.rc != 0
|
|
||||||
and 'No such file or directory' not in (_desktop_audio_result.stderr | default(''))
|
|
||||||
and 'does not exist' not in (_desktop_audio_result.stderr | default(''))
|
|
||||||
|
|
||||||
- name: Assert greetd has a real session command to launch
|
|
||||||
when:
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm == 'greetd'
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- _greetd_session | length > 0
|
|
||||||
- not (_greetd_session | trim | regex_search('\\.desktop$'))
|
|
||||||
fail_msg: >-
|
|
||||||
greetd needs an executable session command, but the resolved command for desktop
|
|
||||||
environment '{{ system_cfg.features.desktop.environment }}' is
|
|
||||||
'{{ _greetd_session }}'. greetd suits wlroots compositors (sway, hyprland) that
|
|
||||||
launch from a plain command; kde/gnome ship a '.desktop' session and should use
|
|
||||||
their own display manager (sddm, gdm). Set features.desktop.session to an
|
|
||||||
executable, or pick a different display manager.
|
|
||||||
|
|
||||||
- name: Generate greetd configuration
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm == 'greetd'
|
|
||||||
- _greetd_session | length > 0
|
|
||||||
block:
|
|
||||||
- name: Ensure greetd config directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/greetd
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Write greetd config.toml
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: greetd-config.toml.j2
|
|
||||||
dest: /mnt/etc/greetd/config.toml
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Configure GDM autologin
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm == 'gdm'
|
|
||||||
- _desktop_autologin_user | length > 0
|
|
||||||
vars:
|
|
||||||
# Debian gdm3 reads daemon.conf; RedHat/Arch gdm read custom.conf.
|
|
||||||
_gdm_dir: "/mnt/etc/{{ 'gdm3' if os_family == 'Debian' else 'gdm' }}"
|
|
||||||
_gdm_conf: "{{ 'daemon.conf' if os_family == 'Debian' else 'custom.conf' }}"
|
|
||||||
block:
|
|
||||||
- name: Ensure GDM config directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ _gdm_dir }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Write GDM autologin config
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: gdm-custom.conf.j2
|
|
||||||
dest: "{{ _gdm_dir }}/{{ _gdm_conf }}"
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
# SDDM and plasma-login-manager share the [Autologin] format and the KDE Wayland
|
|
||||||
# session; only the config dir differs (sddm.conf.d vs plasmalogin.conf.d).
|
|
||||||
- name: Configure SDDM / plasma-login-manager autologin
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm in ['sddm', 'plasmalogin']
|
|
||||||
- _desktop_autologin_user | length > 0
|
|
||||||
vars:
|
|
||||||
_autologin_conf_dir: "/mnt/etc/{{ 'plasmalogin.conf.d' if _desktop_dm == 'plasmalogin' else 'sddm.conf.d' }}"
|
|
||||||
block:
|
|
||||||
- name: Ensure KDE login-manager config directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ _autologin_conf_dir }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
# Plasma 6 ships the Wayland session as plasma.desktop; Plasma 5 ships it as
|
|
||||||
# plasmawayland.desktop (plasma.desktop is the X11 session there). Pick the
|
|
||||||
# installed Wayland session so autologin never lands on X11.
|
|
||||||
- name: Discover installed KDE Wayland sessions
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: /mnt/usr/share/wayland-sessions
|
|
||||||
patterns: "plasma.desktop,plasmawayland.desktop"
|
|
||||||
register: _kde_wayland_sessions
|
|
||||||
|
|
||||||
- name: Resolve the KDE Wayland session file
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
_sddm_session: >-
|
|
||||||
{%- set names = _kde_wayland_sessions.files | map(attribute='path') | map('basename') | list -%}
|
|
||||||
{{ 'plasma.desktop' if 'plasma.desktop' in names else (names | first | default('')) }}
|
|
||||||
|
|
||||||
- name: Write KDE login-manager autologin drop-in
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: sddm-autologin.conf.j2
|
|
||||||
dest: "{{ _autologin_conf_dir }}/10-autologin.conf"
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
# ly ships a flat (sectionless) config.ini; edit it in place to keep upstream
|
|
||||||
# defaults. Both keys are required: an unresolved session writes 'null', which
|
|
||||||
# disables autologin rather than leaving it half-configured.
|
|
||||||
- name: Configure ly autologin
|
|
||||||
when:
|
|
||||||
- _configuration_platform.init_system == 'systemd'
|
|
||||||
- system_cfg.features.desktop.enabled | bool
|
|
||||||
- _desktop_dm == 'ly'
|
|
||||||
- _desktop_autologin_user | length > 0
|
|
||||||
community.general.ini_file:
|
|
||||||
path: /mnt/etc/ly/config.ini
|
|
||||||
option: "{{ item.key }}"
|
|
||||||
value: "{{ item.value }}"
|
|
||||||
create: false
|
|
||||||
mode: "0644"
|
|
||||||
loop:
|
|
||||||
- key: auto_login_user
|
|
||||||
value: "{{ _desktop_autologin_user }}"
|
|
||||||
- key: auto_login_session
|
|
||||||
value: "{{ _greetd_session if (_greetd_session | length > 0) else 'null' }}"
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
# Bootstrap-only: permissive SSH for initial Ansible access.
|
|
||||||
# Post-bootstrap hardening (key-only, no root login) is handled by the linux role.
|
|
||||||
- name: Ensure SSH password authentication is enabled
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/ssh/sshd_config
|
|
||||||
regexp: "^#?PasswordAuthentication\\s+"
|
|
||||||
line: "PasswordAuthentication yes"
|
|
||||||
|
|
||||||
- name: SSH permit root login
|
|
||||||
ansible.builtin.replace:
|
|
||||||
path: /mnt/etc/ssh/sshd_config
|
|
||||||
regexp: "^#?PermitRootLogin.*"
|
|
||||||
replace: "PermitRootLogin yes"
|
|
||||||
@@ -1,30 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: Ensure sudoers.d directory exists
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /mnt/etc/sudoers.d
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Give sudo access to wheel group
|
- name: Give sudo access to wheel group
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "{{ _configuration_platform.sudo_group }} ALL=(ALL) ALL\n"
|
content: "{{ '%sudo ALL=(ALL) ALL' if is_debian | bool else '%wheel ALL=(ALL) ALL' }}"
|
||||||
dest: /mnt/etc/sudoers.d/01-wheel
|
dest: /mnt/etc/sudoers.d/01-wheel
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
|
|
||||||
- name: Deploy per-user sudoers rules
|
|
||||||
# Jinja truthiness: bool true / a rule string => deploy; false / '' / unset => skip.
|
|
||||||
when: item.value.sudo | default(false)
|
|
||||||
vars:
|
|
||||||
configuration_sudoers_rule: >-
|
|
||||||
{{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "{{ item.key }} {{ configuration_sudoers_rule }}\n"
|
|
||||||
dest: "/mnt/etc/sudoers.d/{{ item.key }}"
|
|
||||||
mode: "0440"
|
|
||||||
validate: /usr/sbin/visudo --check --file=%s
|
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.key }}"
|
|
||||||
|
|||||||
@@ -1,73 +1,37 @@
|
|||||||
---
|
---
|
||||||
- name: Set root password
|
- name: Create user account
|
||||||
when: (system_cfg.root.password | default('') | string | length) > 0
|
|
||||||
ansible.builtin.shell: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
echo 'root:{{ system_cfg.root.password if (system_cfg.root.password | string)[:1] == "$" else system_cfg.root.password | password_hash("sha512") }}'
|
|
||||||
| {{ chroot_command }} /usr/sbin/chpasswd -e
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
register: configuration_root_result
|
|
||||||
changed_when: configuration_root_result.rc == 0
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Lock root account when no password is set
|
|
||||||
when: (system_cfg.root.password | default('') | string | length) == 0
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} /usr/bin/passwd -l root
|
|
||||||
register: configuration_root_lock_result
|
|
||||||
changed_when: configuration_root_lock_result.rc == 0
|
|
||||||
|
|
||||||
- name: Set root shell
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} /usr/sbin/usermod --shell {{ system_cfg.root.shell }} root
|
|
||||||
register: configuration_root_shell_result
|
|
||||||
changed_when: configuration_root_shell_result.rc == 0
|
|
||||||
|
|
||||||
- name: Create user accounts
|
|
||||||
vars:
|
vars:
|
||||||
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
configuration_user_group: >-
|
||||||
# plaintext is hashed; a pre-computed crypt hash ($6$/$y$/...) passes through.
|
{{ "sudo" if is_debian | bool else "wheel" }}
|
||||||
configuration_user_pw: >-
|
|
||||||
{{ item.value.password if (item.value.password | string)[:1] == '$'
|
|
||||||
else item.value.password | password_hash('sha512') }}
|
|
||||||
configuration_useradd_cmd: >-
|
configuration_useradd_cmd: >-
|
||||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
arch-chroot /mnt /usr/sbin/useradd --create-home --user-group
|
||||||
--uid {{ 1000 + _idx }}
|
--groups {{ configuration_user_group }} {{ user_name }}
|
||||||
--groups {{ configuration_user_group }} {{ item.key }}
|
--password {{ user_password | password_hash('sha512') }} --shell /bin/bash
|
||||||
{{ ('--password ' ~ configuration_user_pw) if (item.value.password | default('') | string | length > 0) else '' }}
|
configuration_root_cmd: >-
|
||||||
--shell {{ item.value.shell | default('/bin/bash') }}
|
arch-chroot /mnt /usr/sbin/usermod --password
|
||||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
'{{ root_password | password_hash('sha512') }}' root --shell /bin/bash
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
ansible.builtin.command: "{{ item }}"
|
||||||
loop_control:
|
loop:
|
||||||
index_var: _idx
|
- "{{ configuration_useradd_cmd }}"
|
||||||
label: "{{ item.key }}"
|
- "{{ configuration_root_cmd }}"
|
||||||
register: configuration_user_result
|
register: configuration_user_result
|
||||||
changed_when: configuration_user_result.rc == 0
|
changed_when: configuration_user_result.rc == 0
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Ensure .ssh directory exists
|
- name: Ensure .ssh directory exists
|
||||||
when: ('keys' in item.value) and (item.value['keys'] | length) > 0
|
when: user_public_key | length > 0
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "/mnt/home/{{ item.key }}/.ssh"
|
path: /mnt/home/{{ user_name }}/.ssh
|
||||||
state: directory
|
state: directory
|
||||||
owner: "{{ 1000 + _idx }}"
|
owner: 1000
|
||||||
group: "{{ 1000 + _idx }}"
|
group: 1000
|
||||||
mode: "0700"
|
mode: "0700"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
|
||||||
loop_control:
|
|
||||||
index_var: _idx
|
|
||||||
label: "{{ item.key }}"
|
|
||||||
|
|
||||||
- name: Deploy SSH authorized_keys
|
- name: Add SSH public key to authorized_keys
|
||||||
when: ('keys' in item.value) and (item.value['keys'] | length) > 0
|
when: user_public_key | length > 0
|
||||||
ansible.builtin.copy:
|
ansible.builtin.lineinfile:
|
||||||
content: "{{ item.value['keys'] | join('\n') }}\n"
|
path: /mnt/home/{{ user_name }}/.ssh/authorized_keys
|
||||||
dest: "/mnt/home/{{ item.key }}/.ssh/authorized_keys"
|
line: "{{ user_public_key }}"
|
||||||
owner: "{{ 1000 + _idx }}"
|
owner: 1000
|
||||||
group: "{{ 1000 + _idx }}"
|
group: 1000
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
create: true
|
||||||
loop_control:
|
|
||||||
index_var: _idx
|
|
||||||
label: "{{ item.key }}"
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# Managed by Ansible.
|
|
||||||
{% set release = _debian_release_map[os_version | string] | default('trixie') %}
|
|
||||||
{% set mirror = system_cfg.content.url | default('http://deb.debian.org/debian', true) %}
|
|
||||||
{% set components = 'main contrib non-free non-free-firmware' %}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }} {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }} {{ components }}
|
|
||||||
{% if release != 'sid' %}
|
|
||||||
|
|
||||||
deb https://security.debian.org/debian-security {{ release }}-security {{ components }}
|
|
||||||
deb-src https://security.debian.org/debian-security {{ release }}-security {{ components }}
|
|
||||||
|
|
||||||
deb {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
deb-src {{ mirror }} {{ release }}-updates {{ components }}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[{{ os }}{{ os_version_major }}-baseos]
|
|
||||||
name={{ os }} {{ os_version_major }} BaseOS
|
|
||||||
baseurl={{ system_cfg.content.url }}/BaseOS
|
|
||||||
enabled=1
|
|
||||||
gpgcheck={{ 1 if system_cfg.content.gpgcheck | bool else 0 }}
|
|
||||||
{% if system_cfg.content.proxy | length > 0 %}
|
|
||||||
proxy={{ system_cfg.content.proxy }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
[{{ os }}{{ os_version_major }}-appstream]
|
|
||||||
name={{ os }} {{ os_version_major }} AppStream
|
|
||||||
baseurl={{ system_cfg.content.url }}/AppStream
|
|
||||||
enabled=1
|
|
||||||
gpgcheck={{ 1 if system_cfg.content.gpgcheck | bool else 0 }}
|
|
||||||
{% if system_cfg.content.proxy | length > 0 %}
|
|
||||||
proxy={{ system_cfg.content.proxy }}
|
|
||||||
{% endif %}
|
|
||||||
145
roles/configuration/templates/firstrun.sh.j2
Normal file
145
roles/configuration/templates/firstrun.sh.j2
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[1;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Ask for and set the hostname
|
||||||
|
echo -e "${BLUE}Enter the hostname:${NC}"
|
||||||
|
read -r new_hostname
|
||||||
|
|
||||||
|
# Detect the network interface
|
||||||
|
network_interface=$(nmcli -t -f DEVICE connection show --active | head -n 1)
|
||||||
|
|
||||||
|
# Ask for and set the IP address
|
||||||
|
echo -e "${BLUE}Enter the IP address (eg.: 10.11.x.x/24):${NC}"
|
||||||
|
read -r ip_address
|
||||||
|
|
||||||
|
# Ask for and set the DNS server
|
||||||
|
default_dns1="10.11.23.10"
|
||||||
|
default_dns2="10.11.23.18"
|
||||||
|
echo -e "${BLUE}Enter the DNS server (default: $default_dns1, $default_dns2):${NC}"
|
||||||
|
read -r dns_server
|
||||||
|
dns_server=${dns_server:-"$default_dns1 $default_dns2"}
|
||||||
|
|
||||||
|
# Ask if Btrfs compression should be enabled
|
||||||
|
if [[ $(df -T / | awk 'NR==2 {print $2}') == "btrfs" ]]; then
|
||||||
|
echo -e "${BLUE}Do you want to enable Btrfs compression? (y/n):${NC}"
|
||||||
|
read -r enable_compression
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$enable_compression" == "y" || "$enable_compression" == "Y" ]]; then
|
||||||
|
# Ask for the use case
|
||||||
|
echo -e "${BLUE} the use case:${NC}"
|
||||||
|
echo "1. Databases, File Storage, etc (recommended compression level: 15)"
|
||||||
|
echo "2. Real-time compression (recommended compression level: 3)"
|
||||||
|
echo "3. Custom compression level"
|
||||||
|
read -r use_case
|
||||||
|
|
||||||
|
# Set the recommended compression level based on the use case
|
||||||
|
case "$use_case" in
|
||||||
|
1) compression_level=15 ;;
|
||||||
|
2) compression_level=3 ;;
|
||||||
|
3) echo -e "${BLUE}Enter the custom compression level (1-15):${NC}"
|
||||||
|
read -r compression_level ;;
|
||||||
|
*) echo -e "${RED}Invalid use case. Exiting script.${NC}"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask if CheckMK Agent should be installed
|
||||||
|
echo -e "${BLUE}Do you want to install the CheckMK Agent? (y/n):${NC}"
|
||||||
|
read -r install_checkmk_agent
|
||||||
|
|
||||||
|
# Ask if ports and services should be opened
|
||||||
|
echo -e "${BLUE}Do you want to open any ports or services? (y/n):${NC}"
|
||||||
|
read -r open_ports_services
|
||||||
|
|
||||||
|
if [[ "$open_ports_services" == "y" || "$open_ports_services" == "Y" ]]; then
|
||||||
|
# Ask for and set the services to open
|
||||||
|
echo -e "${BLUE}Enter the services to open (comma-separated):${NC}"
|
||||||
|
read -r services
|
||||||
|
|
||||||
|
# Ask for and set the ports to open
|
||||||
|
echo -e "${BLUE}Enter the ports to open (comma-separated):${NC}"
|
||||||
|
read -r ports
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply Changes
|
||||||
|
echo -e "${BLUE}Are you sure you want to apply the changes? This may cause a loss of SSH connection. (y/n):${NC}"
|
||||||
|
read -r answer
|
||||||
|
|
||||||
|
# Check the user's response
|
||||||
|
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
|
||||||
|
# Comment out the script execution line in .bashrc
|
||||||
|
sed -i '/~\/firstrun\.sh/s/^/#/' ~/.bashrc
|
||||||
|
hostnamectl set-hostname "$new_hostname"
|
||||||
|
|
||||||
|
nmcli device modify "$network_interface" ipv4.dns "$dns_server" > /dev/null
|
||||||
|
nmcli device modify "$network_interface" ipv6.method ignore > /dev/null
|
||||||
|
nmcli device modify "$network_interface" ipv4.addresses "$ip_address" ipv4.method manual > /dev/null
|
||||||
|
|
||||||
|
# Modify /etc/hosts file
|
||||||
|
ip_address=$(echo "$ip_address" | sed 's/.\{3\}$//')
|
||||||
|
if grep "$ip_address" /etc/hosts > /dev/null 2>&1; then
|
||||||
|
echo "IP address already exists in /etc/hosts"
|
||||||
|
else
|
||||||
|
# Add IP address and hostname after the "127.0.0.1 localhost" entry
|
||||||
|
sed -i '1a\'"$ip_address\t$new_hostname" /etc/hosts
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "IP address and hostname added to /etc/hosts"
|
||||||
|
else
|
||||||
|
echo "Failed to add IP address and hostname to /etc/hosts"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Modify Btrfs compression settings in /etc/fstab
|
||||||
|
if [[ "$enable_compression" == "y" || "$enable_compression" == "Y" ]]; then
|
||||||
|
if ! grep -q "compress=zstd" /etc/fstab; then
|
||||||
|
sed -i "/btrfs/s/defaults/defaults,compress=zstd:$compression_level/" /etc/fstab
|
||||||
|
else
|
||||||
|
sed -i "/btrfs/s/compress=zstd:[0-9]*/compress=zstd:$compression_level/" /etc/fstab
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if grep -q "compress=zstd" /etc/fstab; then
|
||||||
|
sed -i "/btrfs/s/,compress=zstd:[0-9]*//" /etc/fstab
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$install_checkmk_agent" == "y" || "$install_checkmk_agent" == "Y" ]]; then
|
||||||
|
# Run the CheckMK Agent installation script
|
||||||
|
bash Scripts/install_checkmk_agent.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$open_ports_services" == "y" || "$open_ports_services" == "Y" ]]; then
|
||||||
|
# Open the specified services
|
||||||
|
IFS=',' read -ra service_array <<< "$services"
|
||||||
|
for service in "${service_array[@]}"; do
|
||||||
|
firewall-cmd --add-service="$service" --permanent > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
# Open the specified ports
|
||||||
|
IFS=',' read -ra port_array <<< "$ports"
|
||||||
|
for port in "${port_array[@]}"; do
|
||||||
|
firewall-cmd --add-port="$port"/tcp --permanent > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
firewall-cmd --reload > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Open port 6556/tcp for CheckMK Agent if it was installed
|
||||||
|
if [[ "$install_checkmk_agent" == "y" || "$install_checkmk_agent" == "Y" ]]; then
|
||||||
|
firewall-cmd --add-port=6556/tcp --permanent > /dev/null 2>&1
|
||||||
|
firewall-cmd --reload > /dev/null 2>&1
|
||||||
|
else
|
||||||
|
firewall-cmd --remove-port=6556/tcp --permanent > /dev/null 2>&1
|
||||||
|
firewall-cmd --reload > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Changes applied successfully.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Changes not applied. Exiting script.${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[daemon]
|
|
||||||
WaylandEnable=true
|
|
||||||
AutomaticLoginEnable=true
|
|
||||||
AutomaticLogin={{ _desktop_autologin_user }}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[terminal]
|
|
||||||
vt = 1
|
|
||||||
|
|
||||||
[default_session]
|
|
||||||
command = "tuigreet --time --remember --cmd {{ _greetd_session }}"
|
|
||||||
user = "greeter"
|
|
||||||
{% if _desktop_autologin_user | length > 0 %}
|
|
||||||
|
|
||||||
[initial_session]
|
|
||||||
command = "{{ _greetd_session }}"
|
|
||||||
user = "{{ _desktop_autologin_user }}"
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
[connection]
|
[connection]
|
||||||
id=LAN-{{ idx }}
|
id=LAN
|
||||||
uuid={{ configuration_net_uuid }}
|
uuid={{ configuration_net_uuid }}
|
||||||
type=ethernet
|
type=ethernet
|
||||||
autoconnect-priority=10
|
|
||||||
{% set iface = configuration_iface %}
|
|
||||||
{% if iface.name | default('') | string | length %}
|
|
||||||
interface-name={{ iface.name }}
|
|
||||||
|
|
||||||
{% else %}
|
[ethernet]
|
||||||
{# Bind the first available ethernet by name glob, never a MAC: a clone with a new adapter/MAC stays networked (#12). #}
|
mac-address={{ configuration_net_mac }}
|
||||||
|
|
||||||
[match]
|
|
||||||
interface-name=en*;eth*;
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
[ipv4]
|
[ipv4]
|
||||||
{% set dns_list = configuration_dns_list %}
|
{% set dns_value = vm_dns if vm_dns is defined else '' %}
|
||||||
{% set search_list = configuration_dns_search %}
|
{% set dns_list_raw = dns_value if dns_value is iterable and dns_value is not string else dns_value.split(',') %}
|
||||||
{% if iface.ip | default('') | string | length %}
|
{% set dns_list = dns_list_raw | map('trim') | reject('equalto', '') | list %}
|
||||||
address1={{ iface.ip }}/{{ iface.prefix }}{{ (',' ~ iface.gateway) if (iface.gateway | default('') | string | length) else '' }}
|
{% set search_value = vm_dns_search if vm_dns_search is defined else '' %}
|
||||||
|
{% set search_list_raw = search_value if search_value is iterable and search_value is not string else search_value.split(',') %}
|
||||||
|
{% set search_list = search_list_raw | map('trim') | reject('equalto', '') | list %}
|
||||||
|
{% if vm_ip is defined and vm_ip | length %}
|
||||||
|
address1={{ vm_ip }}/{{ vm_nms }}{{ (',' ~ vm_gw) if (vm_gw is defined and vm_gw | length) else '' }}
|
||||||
method=manual
|
method=manual
|
||||||
{% else %}
|
{% else %}
|
||||||
method=auto
|
method=auto
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if idx | int == 0 and dns_list %}
|
{% if dns_list %}
|
||||||
dns={{ dns_list | join(';') }};
|
dns={{ dns_list | join(';') }}
|
||||||
|
{% endif %}
|
||||||
|
{% if dns_list or search_list %}
|
||||||
ignore-auto-dns=true
|
ignore-auto-dns=true
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if idx | int == 0 and search_list %}
|
{% if search_list %}
|
||||||
dns-search={{ search_list | join(';') }};
|
dns-search={{ search_list | join(';') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
[ipv6]
|
[ipv6]
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
auto lo
|
|
||||||
iface lo inet loopback
|
|
||||||
|
|
||||||
{% for iface in system_cfg.network.interfaces %}
|
|
||||||
{% set ifname = iface.name if (iface.name | default('') | string | length) else (configuration_eni_detected[loop.index0] | default('eth' ~ loop.index0)) %}
|
|
||||||
auto {{ ifname }}
|
|
||||||
{% if iface.ip | default('') | string | length %}
|
|
||||||
iface {{ ifname }} inet static
|
|
||||||
address {{ iface.ip }}/{{ iface.prefix }}
|
|
||||||
{% if iface.gateway | default('') | string | length %}
|
|
||||||
gateway {{ iface.gateway }}
|
|
||||||
{% endif %}
|
|
||||||
{% if loop.index0 == 0 and configuration_dns_list %}
|
|
||||||
dns-nameservers {{ configuration_dns_list | join(' ') }}
|
|
||||||
{% endif %}
|
|
||||||
{% if loop.index0 == 0 and configuration_dns_search %}
|
|
||||||
dns-search {{ configuration_dns_search | join(' ') }}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
iface {{ ifname }} inet dhcp
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
network:
|
|
||||||
version: 2
|
|
||||||
ethernets:
|
|
||||||
{% for iface in system_cfg.network.interfaces %}
|
|
||||||
lan{{ loop.index0 }}:
|
|
||||||
{# Unnamed binds the first ethernet by name glob (e* = en*/eth*, netplan match.name takes one glob), never a MAC (#12). #}
|
|
||||||
match:
|
|
||||||
name: "{{ iface.name if (iface.name | default('') | string | length) else 'e*' }}"
|
|
||||||
{% if iface.ip | default('') | string | length %}
|
|
||||||
addresses:
|
|
||||||
- {{ iface.ip }}/{{ iface.prefix }}
|
|
||||||
{% if iface.gateway | default('') | string | length %}
|
|
||||||
routes:
|
|
||||||
- to: default
|
|
||||||
via: {{ iface.gateway }}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
dhcp4: true
|
|
||||||
{% endif %}
|
|
||||||
{% if loop.index0 == 0 and (configuration_dns_list or configuration_dns_search) %}
|
|
||||||
nameservers:
|
|
||||||
{% if configuration_dns_list %}
|
|
||||||
addresses: [{{ configuration_dns_list | join(', ') }}]
|
|
||||||
{% endif %}
|
|
||||||
{% if configuration_dns_search %}
|
|
||||||
search: [{{ configuration_dns_search | join(', ') }}]
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
[Match]
|
|
||||||
{% set iface = configuration_iface %}
|
|
||||||
{% if iface.name | default('') | string | length %}
|
|
||||||
Name={{ iface.name }}
|
|
||||||
{% else %}
|
|
||||||
{# First available ethernet by name glob + device type, never a MAC (#12). #}
|
|
||||||
Name=en* eth*
|
|
||||||
Type=ether
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
{% if iface.ip | default('') | string | length %}
|
|
||||||
Address={{ iface.ip }}/{{ iface.prefix }}
|
|
||||||
{% if iface.gateway | default('') | string | length %}
|
|
||||||
Gateway={{ iface.gateway }}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
DHCP=yes
|
|
||||||
{% endif %}
|
|
||||||
{% if idx | int == 0 and configuration_dns_list %}
|
|
||||||
{% for dns in configuration_dns_list %}
|
|
||||||
DNS={{ dns }}
|
|
||||||
{% endfor %}
|
|
||||||
{% if configuration_dns_search %}
|
|
||||||
Domains={{ configuration_dns_search | join(' ') }}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{% set _session = _desktop_session if (_desktop_session | length > 0) else _sddm_session %}
|
|
||||||
[Autologin]
|
|
||||||
User={{ _desktop_autologin_user }}
|
|
||||||
{% if _session | length > 0 %}
|
|
||||||
Session={{ _session }}
|
|
||||||
{% endif %}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user