Compare commits
156 Commits
master
...
9e7fc156ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e7fc156ab | |||
| 7e9abe862f | |||
| 5aa5022983 | |||
| 74ae98db35 | |||
| fc23f84cc3 | |||
| 3c7d9e16da | |||
| e4b9f1c579 | |||
| 920969d60e | |||
| 9d723630cb | |||
| 0c8242589c | |||
| 2885ba9ffa | |||
| 81d63029a4 | |||
| 2fa0fba4c4 | |||
| 055b6de68b | |||
| 4e85740e0a | |||
| 0ee2806c62 | |||
| 1027afc6ea | |||
| 74cb09ffee | |||
| 9f5096d69d | |||
| 6da46a03ed | |||
| e7c898d653 | |||
| 0388dca0a4 | |||
| 1d545fbbc8 | |||
| 53bb4589b6 | |||
| 73f0b81b5a | |||
| 2d46df8f5a | |||
| 45d3fef4e2 | |||
| a6b051d9e4 | |||
| 8056890460 | |||
| 085e16abe9 | |||
| 23f08b350b | |||
| 315fdef69f | |||
| 2d4127a688 | |||
| 1cc1966b97 | |||
| 4d72a8999f | |||
| e264d1cabc | |||
| aa6e356444 | |||
| fe0b72c9d8 | |||
| ce972e55dd | |||
| 2891de8fef | |||
| 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
|
|
||||||
706
README.md
706
README.md
@@ -1,8 +1,8 @@
|
|||||||
# 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 Linux system bootstrap in an Infrastructure-as-Code manner. It uses the Arch Linux ISO as a foundational tool to provide an efficient and systematic method for the automatic deployment of a variety of Linux distributions on designated target systems, ensuring a standardized setup across different platforms.
|
||||||
|
|
||||||
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.
|
Most roles are adaptable for use with systems beyond Arch Linux, requiring only that the target system can install the necessary package manager (e.g. `dnf` for RHEL-based systems). A replacement for the `arch-chroot` command may also be required; set `system.features.chroot.tool` accordingly.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -13,30 +13,34 @@ Non-Arch targets require the appropriate package manager available from the ISO
|
|||||||
- 4.1 [Core Variables](#41-core-variables)
|
- 4.1 [Core Variables](#41-core-variables)
|
||||||
- 4.2 [`system` Dictionary](#42-system-dictionary)
|
- 4.2 [`system` Dictionary](#42-system-dictionary)
|
||||||
- 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary)
|
- 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary)
|
||||||
- 4.4 [CIS Hardening](#44-cis-hardening)
|
- 4.4 [VMware Guest Operations](#44-vmware-guest-operations)
|
||||||
- 4.5 [VMware Guest Operations](#45-vmware-guest-operations)
|
- 4.5 [Multi-Disk Schema](#45-multi-disk-schema)
|
||||||
- 4.6 [Multi-Disk Schema](#46-multi-disk-schema)
|
- 4.6 [Advanced Partitioning Overrides](#46-advanced-partitioning-overrides)
|
||||||
- 4.7 [Advanced Partitioning Overrides](#47-advanced-partitioning-overrides)
|
5. [How to Use the Playbook](#5-how-to-use-the-playbook)
|
||||||
- 4.8 [Cleanup Defaults](#48-cleanup-defaults)
|
- 5.1 [Prerequisites](#51-prerequisites)
|
||||||
5. [Execution Pipeline](#5-execution-pipeline)
|
- 5.2 [Running the Playbook](#52-running-the-playbook)
|
||||||
6. [Usage](#6-usage)
|
- 5.3 [Example Usage](#53-example-usage)
|
||||||
7. [Security](#7-security)
|
6. [Security](#6-security)
|
||||||
|
7. [Operational Notes](#7-operational-notes)
|
||||||
8. [Safety](#8-safety)
|
8. [Safety](#8-safety)
|
||||||
|
|
||||||
## 1. Supported Platforms
|
## 1. Supported Platforms
|
||||||
|
|
||||||
### Distributions
|
### Distributions
|
||||||
|
|
||||||
| `system.os` | Distribution | `system.version` |
|
| `system.os` | Distribution | `system.version` |
|
||||||
| ------------ | ------------------------ | ------------------------------------- |
|
| ------------ | ------------------------ | ------------------------------- |
|
||||||
| `almalinux` | AlmaLinux | `9`, `10` |
|
| `almalinux` | AlmaLinux | `8`, `9`, `10` |
|
||||||
| `archlinux` | Arch Linux | latest (rolling) |
|
| `alpine` | Alpine Linux | latest (rolling) |
|
||||||
| `debian` | Debian | `12`, `13`, `unstable` |
|
| `archlinux` | Arch Linux | latest (rolling) |
|
||||||
| `fedora` | Fedora | `43`, `44` |
|
| `debian` | Debian | `10`, `11`, `12`, `13`, `unstable` |
|
||||||
| `rhel` | Red Hat Enterprise Linux | `9`, `10` |
|
| `fedora` | Fedora | `40`, `41`, `42`, `43` |
|
||||||
| `rocky` | Rocky Linux | `9`, `10` |
|
| `opensuse` | openSUSE Tumbleweed | latest (rolling) |
|
||||||
| `ubuntu` | Ubuntu (latest non-LTS) | optional (tracks 25.10 `questing`) |
|
| `rhel` | Red Hat Enterprise Linux | `8`, `9`, `10` |
|
||||||
| `ubuntu-lts` | Ubuntu LTS | optional (tracks 26.04 `resolute`) |
|
| `rocky` | Rocky Linux | `8`, `9`, `10` |
|
||||||
|
| `ubuntu` | Ubuntu | latest |
|
||||||
|
| `ubuntu-lts` | Ubuntu LTS | latest |
|
||||||
|
| `void` | Void Linux | latest (rolling) |
|
||||||
|
|
||||||
### Hypervisors
|
### Hypervisors
|
||||||
|
|
||||||
@@ -51,26 +55,28 @@ Non-Arch targets require the appropriate package manager available from the ISO
|
|||||||
## 2. Compatibility Notes
|
## 2. Compatibility Notes
|
||||||
|
|
||||||
- `rhel_iso` is required for `system.os: rhel`.
|
- `rhel_iso` is required for `system.os: rhel`.
|
||||||
- RHEL installs should use `ext4` or `xfs` (not `btrfs`).
|
- RHEL installs should use `system.filesystem: ext4` or `system.filesystem: xfs` (not `btrfs`).
|
||||||
- `custom_iso: true` skips ArchISO validation; your installer must provide required tooling.
|
- For RHEL 8 specifically, prefer `ext4` over `xfs` if you hit installer/filesystem edge cases.
|
||||||
- On non-Arch installers, set `system.features.chroot.tool` explicitly.
|
- `custom_iso: true` skips ArchISO validation and pacman preparation; your installer image must already provide required tooling.
|
||||||
|
- On non-Arch installers, set `system.features.chroot.tool` (`arch-chroot`, `chroot`, or `systemd-nspawn`) explicitly as needed.
|
||||||
|
|
||||||
## 3. Configuration Model
|
## 3. Configuration Model
|
||||||
|
|
||||||
Two dict-based variables drive the entire configuration:
|
The project uses two dict-based variables:
|
||||||
|
|
||||||
- **`system`** -- host, network, users, disk layout, encryption, and feature toggles (including CIS hardening under `system.features.cis`)
|
- `system` for host/runtime/install configuration
|
||||||
- **`hypervisor`** -- virtualization backend credentials and targeting
|
- `hypervisor` for virtualization backend configuration
|
||||||
|
|
||||||
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.
|
These are normal Ansible variables and belong in host/group vars. You can define them in inventory host entries, `group_vars/*`, or `host_vars/*`. Dictionary variables are merged across scopes (`group_vars` -> `host_vars`) by project config (`hash_behaviour = merge`), so you can set shared values like `system.filesystem` once in group vars and override only host-specific keys per host.
|
||||||
|
|
||||||
### Variable Placement
|
### Variable Placement
|
||||||
|
|
||||||
| Location | Scope | Typical use |
|
| Location | Scope | Typical use |
|
||||||
| ------------------------ | ----------- | -------------------------------------------------------------- |
|
| -------------------------- | ----------- | ------------------------------------------------------------ |
|
||||||
| `group_vars/all.yml` | All hosts | Shared `hypervisor`, `system.filesystem`, `boot_iso` |
|
| `group_vars/all.yml` | All hosts | Shared defaults like `hypervisor`, `system.filesystem`, `boot_iso` |
|
||||||
| `group_vars/<group>.yml` | Group | Environment-specific defaults |
|
| `group_vars/<group>.yml` | Group | Environment or role-specific defaults |
|
||||||
| `host_vars/<host>.yml` | Single host | Host-specific overrides (`system.network.ip`, `system.id`, etc.) |
|
| `host_vars/<host>.yml` | Single host | Host-specific overrides |
|
||||||
|
| Inventory inline host vars | Single host | Inline definitions for quick setup |
|
||||||
|
|
||||||
### Example Inventory
|
### Example Inventory
|
||||||
|
|
||||||
@@ -84,9 +90,8 @@ all:
|
|||||||
type: proxmox
|
type: proxmox
|
||||||
url: pve01.example.com
|
url: pve01.example.com
|
||||||
username: root@pam
|
username: root@pam
|
||||||
password: !vault |
|
password: CHANGE_ME
|
||||||
$ANSIBLE_VAULT...
|
host: pve01
|
||||||
node: pve01
|
|
||||||
storage: local-lvm
|
storage: local-lvm
|
||||||
|
|
||||||
children:
|
children:
|
||||||
@@ -102,40 +107,34 @@ all:
|
|||||||
id: 101
|
id: 101
|
||||||
cpus: 2
|
cpus: 2
|
||||||
memory: 4096
|
memory: 4096
|
||||||
network:
|
balloon: 0
|
||||||
bridge: vmbr0
|
network: vmbr0
|
||||||
ip: 10.0.0.10
|
ip: 10.0.0.10
|
||||||
prefix: 24
|
prefix: 24
|
||||||
gateway: 10.0.0.1
|
gateway: 10.0.0.1
|
||||||
dns:
|
dns:
|
||||||
servers: [1.1.1.1, 1.0.0.1]
|
servers: [1.1.1.1, 1.0.0.1]
|
||||||
search: [example.com]
|
search: [example.com]
|
||||||
disks:
|
disks:
|
||||||
- size: 40
|
- size: 40
|
||||||
- size: 120
|
- size: 120
|
||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: xfs
|
fstype: xfs
|
||||||
users:
|
user:
|
||||||
ops:
|
name: ops
|
||||||
password: !vault |
|
password: CHANGE_ME
|
||||||
$ANSIBLE_VAULT...
|
key: "ssh-ed25519 AAAA..."
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
sudo: true
|
|
||||||
root:
|
root:
|
||||||
password: !vault |
|
password: CHANGE_ME
|
||||||
$ANSIBLE_VAULT...
|
|
||||||
luks:
|
luks:
|
||||||
enabled: true
|
enabled: true
|
||||||
passphrase: !vault |
|
passphrase: CHANGE_ME
|
||||||
$ANSIBLE_VAULT...
|
auto: true
|
||||||
method: tpm2
|
method: tpm2
|
||||||
tpm2:
|
tpm2:
|
||||||
pcrs: "7"
|
pcrs: "7"
|
||||||
features:
|
features:
|
||||||
cis:
|
|
||||||
enabled: true
|
|
||||||
firewall:
|
firewall:
|
||||||
enabled: true
|
enabled: true
|
||||||
backend: firewalld
|
backend: firewalld
|
||||||
@@ -146,476 +145,261 @@ all:
|
|||||||
|
|
||||||
### 4.1 Core Variables
|
### 4.1 Core Variables
|
||||||
|
|
||||||
Top-level variables outside `system`/`hypervisor`.
|
These top-level variables sit outside the `system`/`hypervisor` dictionaries.
|
||||||
|
|
||||||
| Variable | Type | Default | Description |
|
| Variable | Type | Description |
|
||||||
| ---------------- | ------ | -------------------------- | ---------------------------------------------------- |
|
| ------------ | ------ | ------------------------------------------------ |
|
||||||
| `boot_iso` | string | -- | Boot ISO path (required for virtual installs) |
|
| `boot_iso` | string | Path to the boot ISO image (required for virtual installs). |
|
||||||
| `rhel_iso` | string | -- | RHEL ISO path (required when `system.os: rhel`) |
|
| `rhel_iso` | string | Path to the RHEL ISO (required when `system.os: rhel`). |
|
||||||
| `custom_iso` | bool | `false` | Skip ArchISO validation and pacman setup |
|
| `custom_iso` | bool | Skip ArchISO validation and pacman setup. Default `false`. |
|
||||||
| `thirdparty_tasks` | string | `dropins/preparation.yml` | Drop-in task file included during environment setup |
|
|
||||||
|
|
||||||
### 4.2 `system` Dictionary
|
### 4.2 `system` Dictionary
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
Top-level host install/runtime settings. Use these keys under `system`.
|
||||||
| ------------ | ---------- | ------------------ | ------------------------------------------------------ |
|
|
||||||
| `type` | string | `virtual` | `virtual` or `physical` |
|
|
||||||
| `os` | string | -- | Target distribution (see [table](#distributions)) |
|
|
||||||
| `version` | string | -- | Version selector for versioned distros |
|
|
||||||
| `filesystem` | string | `ext4` | `btrfs`, `ext4`, or `xfs` |
|
|
||||||
| `name` | string | inventory hostname | Final hostname |
|
|
||||||
| `timezone` | string | `Europe/Vienna` | System timezone (tz database name) |
|
|
||||||
| `locale` | string | `en_US.UTF-8` | System locale |
|
|
||||||
| `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`
|
| Key | Type | Default | Description |
|
||||||
|
| ------------ | ---------- | -------------------- | ---------------------------------------- |
|
||||||
|
| `type` | string | `virtual` | `virtual` or `physical` |
|
||||||
|
| `os` | string | empty | Target distribution (see [table](#distributions)) |
|
||||||
|
| `version` | string | empty | Version selector for distro families |
|
||||||
|
| `filesystem` | string | empty | `btrfs`, `ext4`, or `xfs` |
|
||||||
|
| `name` | string | inventory hostname | Final hostname |
|
||||||
|
| `id` | int/string | empty | VMID (required for Proxmox) |
|
||||||
|
| `cpus` | int | `0` | vCPU count |
|
||||||
|
| `memory` | int | `0` | Memory in MiB |
|
||||||
|
| `balloon` | int | `0` | Balloon memory in MiB |
|
||||||
|
| `network` | string | empty | Hypervisor network/bridge |
|
||||||
|
| `vlan` | string/int | empty | VLAN tag |
|
||||||
|
| `ip` | string | empty | Static IP (omit for DHCP) |
|
||||||
|
| `prefix` | int | empty | CIDR prefix for static IP |
|
||||||
|
| `gateway` | string | empty | Default gateway (static only) |
|
||||||
|
| `path` | string | empty | Hypervisor folder/path (libvirt/vmware) |
|
||||||
|
| `packages` | list | `[]` | Additional packages installed post-reboot |
|
||||||
|
| `dns` | dict | see below | DNS configuration |
|
||||||
|
| `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#45-multi-disk-schema)) |
|
||||||
|
| `user` | dict | see below | User account settings |
|
||||||
|
| `root` | dict | see below | Root account settings |
|
||||||
|
| `luks` | dict | see below | Encryption settings |
|
||||||
|
| `features` | dict | see below | Feature toggles |
|
||||||
|
|
||||||
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.
|
#### `system.dns`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| -------------------------- | ------ | -------------- | ----------------------------------------------------------------- |
|
| --------- | ----------- | ------- | --------------------------------------------------- |
|
||||||
| `source` | string | family default | `dvd`, `mirror`, `satellite`, or `none` |
|
| `servers` | list/string | `[]` | DNS resolvers; comma-separated string is normalized |
|
||||||
| `url` | string | family default | Mirror URL / EL `.repo` baseurl |
|
| `search` | list/string | `[]` | Search domains; comma-separated string is normalized |
|
||||||
| `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`
|
#### `system.user`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
Credentials are prompted interactively by default via `vars_prompt` in `main.yml`, but can be supplied via inventory, vars files, or `-e` for non-interactive runs.
|
||||||
| -------------- | ---------- | ------- | ---------------------------------------------- |
|
|
||||||
| `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`.
|
| Key | Type | Default | Description |
|
||||||
|
| ---------- | ------ | ------- | ------------------------------------- |
|
||||||
#### `system.users`
|
| `name` | string | empty | Username created on target |
|
||||||
|
| `password` | string | empty | User password (also used for sudo) |
|
||||||
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).
|
| `key` | string | empty | SSH public key for `authorized_keys` |
|
||||||
|
|
||||||
```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`
|
#### `system.root`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| ---------- | ------ | ----------- | ------------- |
|
| ---------- | ------ | ------- | -------------- |
|
||||||
| `password` | string | -- | Root password |
|
| `password` | string | empty | Root password |
|
||||||
| `shell` | string | `/bin/bash` | Login shell |
|
|
||||||
|
|
||||||
#### `system.luks`
|
#### `system.luks`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
LUKS container, unlock, and initramfs-related settings.
|
||||||
| ------------ | ------ | ------------------ | ------------------------------------------ |
|
|
||||||
| `enabled` | bool | `false` | Enable encrypted root |
|
| Key | Type | Default | Allowed | Description |
|
||||||
| `passphrase` | string | -- | Passphrase for format/open/enroll |
|
| ------------ | ------ | ------------------ | -------------------------- | ------------------------------------------ |
|
||||||
| `mapper` | string | `SYSTEM_DECRYPTED` | Mapper name under `/dev/mapper` |
|
| `enabled` | bool | `false` | `true`/`false` | Enable encrypted root workflow |
|
||||||
| `auto` | bool | `true` | Auto-unlock toggle |
|
| `passphrase` | string | empty | any string | Passphrase used for format/open/enroll |
|
||||||
| `method` | string | `tpm2` | Auto-unlock backend: `tpm2` or `keyfile` |
|
| `mapper` | string | `SYSTEM_DECRYPTED` | mapper name | Mapper name under `/dev/mapper` |
|
||||||
| `keysize` | int | `64` | Keyfile size in bytes |
|
| `auto` | bool | `true` | `true`/`false` | Auto-unlock behavior toggle |
|
||||||
| `options` | string | `discard,tries=3` | Additional crypttab options |
|
| `method` | string | `tpm2` | `tpm2`, `keyfile` | Auto-unlock backend when `auto=true` |
|
||||||
| `type` | string | `luks2` | LUKS format type |
|
| `keysize` | int | `64` | positive int | Keyfile size (bytes) for keyfile mode |
|
||||||
| `cipher` | string | `aes-xts-plain64` | Cipher |
|
| `options` | string | `discard,tries=3` | crypttab opts | Additional crypttab/kernel options |
|
||||||
| `hash` | string | `sha512` | Hash algorithm |
|
| `type` | string | `luks2` | cryptsetup type | LUKS format type |
|
||||||
| `iter` | int | `4000` | PBKDF iteration time (ms) |
|
| `cipher` | string | `aes-xts-plain64` | cipher name | Cryptsetup cipher |
|
||||||
| `bits` | int | `512` | Key size (bits) |
|
| `hash` | string | `sha512` | hash name | Cryptsetup hash |
|
||||||
| `pbkdf` | string | `argon2id` | PBKDF algorithm |
|
| `iter` | int | `4000` | positive int | PBKDF iteration time (ms) |
|
||||||
|
| `bits` | int | `512` | positive int | Key size (bits) |
|
||||||
|
| `pbkdf` | string | `argon2id` | pbkdf name | PBKDF algorithm |
|
||||||
|
| `urandom` | bool | `true` | `true`/`false` | Use urandom during key generation |
|
||||||
|
| `verify` | bool | `true` | `true`/`false` | Verify passphrase during format |
|
||||||
|
|
||||||
#### `system.luks.tpm2`
|
#### `system.luks.tpm2`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
TPM2-specific policy settings used when `system.luks.method: tpm2`.
|
||||||
| -------- | ------------- | ------- | ---------------------------------------------- |
|
|
||||||
| `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
|
| Key | Type | Default | Allowed | Description |
|
||||||
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
|
| `device` | string | `auto` | `auto` or device path | TPM2 device selector |
|
||||||
TPM2 can be enrolled post-deployment via `systemd-cryptenroll --tpm2-device=auto <device>`.
|
| `pcrs` | string/list | empty | PCR expression | PCR binding policy (e.g. `"7"` or `"0+7"`) |
|
||||||
|
|
||||||
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`
|
#### `system.features`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
Feature toggles for optional system configuration.
|
||||||
| ------------------ | ------ | -------------- | ------------------------------------ |
|
|
||||||
| `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.
|
| Key | Type | Default | Allowed | Description |
|
||||||
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.
|
| `cis.enabled` | bool | `false` | `true`/`false` | Enable CIS hardening role |
|
||||||
On distros with older dracut (no `tpm2-tss` module), clevis is used as a fallback for TPM2 binding.
|
| `selinux.enabled` | bool | `true` | `true`/`false` | SELinux management |
|
||||||
|
| `firewall.enabled` | bool | `true` | `true`/`false` | Enable firewall role actions |
|
||||||
#### 4.2.5 `system.features.desktop`
|
| `firewall.backend` | string | `firewalld` | `firewalld`, `ufw` | Firewall service backend |
|
||||||
|
| `firewall.toolkit` | string | `nftables` | `nftables`, `iptables` | Packet filtering toolkit |
|
||||||
| Key | Type | Default | Description |
|
| `ssh.enabled` | bool | `true` | `true`/`false` | SSH service/package management |
|
||||||
| ----------------- | ------ | -------------- | ----------------------------------------- |
|
| `zstd.enabled` | bool | `true` | `true`/`false` | zstd related tuning |
|
||||||
| `enabled` | bool | `false` | Install desktop environment |
|
| `swap.enabled` | bool | `true` | `true`/`false` | Swap setup toggle |
|
||||||
| `environment` | string | `""` | `gnome`, `kde`, `sway`, or `hyprland` |
|
| `banner.motd` | bool | `false` | `true`/`false` | MOTD banner management |
|
||||||
| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, `plasma-login-manager`, `greetd`, or `ly` |
|
| `banner.sudo` | bool | `true` | `true`/`false` | Sudo banner management |
|
||||||
| `autologin` | bool \| string | `false` | `false` to disable, or a username from `system.users` to auto-login that user |
|
| `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, `systemd-nspawn` | Chroot wrapper command |
|
||||||
| `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
|
### 4.3 `hypervisor` Dictionary
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Description |
|
||||||
| ------------ | ------ | ------- | ---------------------------------------------------- |
|
| ------------ | ------ | -------------------------------------------------------- |
|
||||||
| `type` | string | -- | `libvirt`, `proxmox`, `vmware`, `xen`, or `none` |
|
| `type` | string | `libvirt`, `proxmox`, `vmware`, `xen`, or `none` |
|
||||||
| `url` | string | -- | API host (Proxmox/VMware) |
|
| `url` | string | Proxmox/VMware API host |
|
||||||
| `username` | string | -- | API username |
|
| `username` | string | API username |
|
||||||
| `password` | string | -- | API password |
|
| `password` | string | API password |
|
||||||
| `node` | string | -- | Target compute node (Proxmox node / VMware ESXi host; mutually exclusive with `cluster` on VMware) |
|
| `host` | string | Proxmox node name |
|
||||||
| `storage` | string | -- | Storage identifier (Proxmox/VMware) |
|
| `storage` | string | Proxmox/VMware storage identifier |
|
||||||
| `datacenter` | string | -- | VMware datacenter |
|
| `datacenter` | string | VMware datacenter |
|
||||||
| `cluster` | string | -- | VMware cluster |
|
| `cluster` | string | VMware cluster |
|
||||||
| `certs` | bool | `false` | TLS certificate validation (VMware) |
|
| `certs` | bool | TLS certificate validation for VMware |
|
||||||
| `ssh` | bool | `false` | Enable SSH on guest and switch connection (VMware) |
|
| `ssh` | bool | VMware: enable SSH on guest and switch connection to SSH |
|
||||||
|
|
||||||
### 4.4 CIS Hardening
|
### 4.4 VMware Guest Operations
|
||||||
|
|
||||||
When `system.features.cis.enabled: true`, the CIS role applies hardening. The behaviour is driven by three keys under `system.features.cis`:
|
When `hypervisor.type: vmware` uses the `vmware_tools` connection, these Ansible connection variables are required.
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Variable | Description |
|
||||||
| --------- | ------ | ----------- | ----------------------------------------------------------------- |
|
| ------------------------------- | -------------------------------------------------- |
|
||||||
| `enabled` | bool | `false` | Apply CIS hardening at all |
|
| `ansible_vmware_tools_user` | Guest OS username for guest operations |
|
||||||
| `profile` | string | `default` | `default` (house baseline), `l1` (clean CIS Level 1), or `l2` |
|
| `ansible_vmware_tools_password` | Guest OS password for guest operations |
|
||||||
| `rules` | dict | `{}` | Per-rule on/off overrides on top of the profile |
|
| `ansible_vmware_guest_path` | VM inventory path (`/datacenter/vm/folder/name`) |
|
||||||
| `params` | dict | `{}` | Parameter overrides (deep-merged; list values replace wholesale) |
|
| `ansible_vmware_host` | vCenter/ESXi hostname |
|
||||||
|
| `ansible_vmware_user` | vCenter/ESXi API username |
|
||||||
|
| `ansible_vmware_password` | vCenter/ESXi API password |
|
||||||
|
| `ansible_vmware_validate_certs` | Enable/disable TLS certificate validation |
|
||||||
|
|
||||||
**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.
|
### 4.5 Multi-Disk Schema
|
||||||
|
|
||||||
**Per-rule overrides.** Toggle an individual rule without changing profile, e.g. keep the default profile but allow USB and IPv6 on a desktop:
|
`system.disks[0]` is always the OS disk. Additional entries define data disks.
|
||||||
|
|
||||||
```yaml
|
| Key | Type | Description |
|
||||||
system:
|
| ------------- | ------ | ---------------------------------------------------- |
|
||||||
features:
|
| `size` | number | Disk size in GB (required for virtual installs) |
|
||||||
cis:
|
| `device` | string | Explicit block device (required for physical data disks) |
|
||||||
enabled: true
|
| `mount.path` | string | Mount point (for additional disks) |
|
||||||
rules:
|
| `mount.fstype`| string | `btrfs`, `ext4`, or `xfs` |
|
||||||
usb_lockdown: false
|
| `mount.label` | string | Optional filesystem label |
|
||||||
ipv6_disable: false
|
| `mount.opts` | string | Mount options (default: `defaults`) |
|
||||||
```
|
|
||||||
|
|
||||||
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`).
|
Virtual install example:
|
||||||
|
|
||||||
**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
|
```yaml
|
||||||
system:
|
system:
|
||||||
disks:
|
disks:
|
||||||
- size: 80 # OS disk
|
- size: 80
|
||||||
- size: 200 # Data disk
|
- size: 200
|
||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: xfs
|
fstype: xfs
|
||||||
label: DATA
|
label: DATA
|
||||||
|
opts: defaults,noatime
|
||||||
|
- size: 300
|
||||||
|
mount:
|
||||||
|
path: /backup
|
||||||
|
fstype: ext4
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.7 Advanced Partitioning Overrides
|
Physical install example (device paths required):
|
||||||
|
|
||||||
| Variable | Default | Description |
|
```yaml
|
||||||
| ------------------------------ | ------------ | ---------------------------------------- |
|
system:
|
||||||
| `partitioning_efi_size_mib` | `512` | EFI system partition size in MiB |
|
type: physical
|
||||||
| `partitioning_boot_size_mib` | `1024` | Separate `/boot` size in MiB |
|
disks:
|
||||||
| `partitioning_separate_boot` | auto-derived | Force a separate `/boot` partition |
|
- device: /dev/sda
|
||||||
| `partitioning_boot_fs_fstype` | auto-derived | Filesystem for `/boot` |
|
size: 120
|
||||||
| `partitioning_use_full_disk` | `true` | Use remaining VG space for root LV |
|
- device: /dev/sdb
|
||||||
|
size: 500
|
||||||
|
mount:
|
||||||
|
path: /data
|
||||||
|
fstype: ext4
|
||||||
|
```
|
||||||
|
|
||||||
**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.
|
### 4.6 Advanced Partitioning Overrides
|
||||||
|
|
||||||
**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).
|
Use these only when you need to override the default partition layout logic.
|
||||||
|
|
||||||
### 4.8 Cleanup Defaults
|
| Variable | Description | Default |
|
||||||
|
| ------------------------------ | ------------------------------------------------- | ------------ |
|
||||||
|
| `partitioning_efi_size_mib` | EFI system partition size in MiB | `512` |
|
||||||
|
| `partitioning_boot_size_mib` | Separate `/boot` size in MiB (when used) | `1024` |
|
||||||
|
| `partitioning_separate_boot` | Force a separate `/boot` partition | auto-derived |
|
||||||
|
| `partitioning_boot_fs_fstype` | Filesystem for `/boot` when separate | auto-derived |
|
||||||
|
| `partitioning_use_full_disk` | Consume remaining VG space for root LV | `true` |
|
||||||
|
|
||||||
Post-install verification and recovery settings.
|
## 5. How to Use the Playbook
|
||||||
|
|
||||||
| Variable | Default | Description |
|
### 5.1 Prerequisites
|
||||||
| --------------------------- | ------- | ----------------------------------------------------- |
|
|
||||||
| `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
|
- Ansible installed on the control machine.
|
||||||
|
- Inventory file with target systems defined and variables configured.
|
||||||
|
- Disposable/non-production targets (the playbook enforces production-safety checks).
|
||||||
|
|
||||||
Roles execute in this order:
|
### 5.2 Running the Playbook
|
||||||
|
|
||||||
1. **global_defaults** -- normalize inputs, validate, set OS flags
|
Execute the playbook using `ansible-playbook`, ensuring that all necessary variables are defined either in the inventory, in a vars file, or passed via `-e`. Credentials (`root_password`, `user_name`, `user_password`, `user_public_key`) are prompted interactively unless supplied through inventory or extra vars.
|
||||||
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 main.yml
|
||||||
ansible-playbook -i inventory.yml main.yml -e @vars.yml
|
ansible-playbook -i inventory.yml main.yml -e @vars_example.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
All credentials (`system.users`, `system.root.password`) must be defined in inventory or passed via `-e`.
|
### 5.3 Example Usage
|
||||||
|
|
||||||
Example inventory files are included:
|
Use the bundled example files as starting points for new inventories:
|
||||||
|
|
||||||
- `inventory_example.yml` -- Proxmox virtual setup
|
- `inventory_example.yml` -- Proxmox virtual setup
|
||||||
- `inventory_libvirt_example.yml` -- libvirt virtual setup
|
- `inventory_libvirt_example.yml` -- libvirt virtual setup
|
||||||
- `inventory_baremetal_example.yml` -- bare-metal physical setup
|
- `inventory_baremetal_example.yml` -- bare-metal physical setup
|
||||||
|
- `vars_example.yml` -- shared variable overrides
|
||||||
|
|
||||||
## 7. Security
|
```bash
|
||||||
|
# Proxmox example
|
||||||
|
ansible-playbook -i inventory_example.yml main.yml
|
||||||
|
|
||||||
Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, user passwords in `system.users`, `system.root.password`).
|
# libvirt example
|
||||||
|
ansible-playbook -i inventory_libvirt_example.yml main.yml
|
||||||
|
|
||||||
|
# Custom inventory with separate vars file
|
||||||
|
ansible-playbook -i inventory.yml main.yml -e @vars_example.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Security
|
||||||
|
|
||||||
|
To protect sensitive information such as passwords, API keys, and other confidential variables (e.g. `hypervisor.password`, `system.luks.passphrase`), **use Ansible Vault** instead of plaintext inventory files.
|
||||||
|
|
||||||
|
## 7. Operational Notes
|
||||||
|
|
||||||
|
- For virtual installs, `system.cpus`, `system.memory`, and `system.disks[0].size` are required and validated.
|
||||||
|
- For physical installs, sizing is derived from the detected install drive; set installer access (`ansible_user`/`ansible_password`) when the installer environment differs from the prompted user credentials.
|
||||||
|
- `system.dns.servers` and `system.dns.search` accept either YAML lists or comma-separated strings.
|
||||||
|
- `hypervisor.type` selects backend-specific provisioning and cleanup behavior.
|
||||||
|
- Guest tools are selected automatically by hypervisor: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`.
|
||||||
|
- With `system.luks.method: tpm2` on virtual installs, the virtualization role enables a TPM2 device where supported (libvirt/proxmox/vmware).
|
||||||
|
- With LUKS enabled on non-Arch targets, provisioning uses an ESP (512 MiB), a separate `/boot` (1 GiB), and the encrypted root; adjust sizes via `partitioning_efi_size_mib` and `partitioning_boot_size_mib` if needed.
|
||||||
|
- For VMware, `hypervisor.ssh: true` enables SSH on the guest and switches the connection to SSH for the remaining tasks.
|
||||||
|
- Molecule is scaffolded with a delegated driver and a no-op converge for lint-only validation.
|
||||||
|
|
||||||
## 8. Safety
|
## 8. Safety
|
||||||
|
|
||||||
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.
|
This playbook intentionally aborts if it detects a non-live/production target. It also refuses to touch pre-existing VMs and only cleans up VMs created in the current run.
|
||||||
|
|
||||||
|
Always run lint after changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-lint
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
hash_behaviour = merge
|
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"
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ all:
|
|||||||
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
|
|
||||||
# install_drive will be wiped. system_check refuses to run without it.
|
|
||||||
physical_install_confirmed: true
|
|
||||||
system:
|
system:
|
||||||
type: "physical"
|
type: "physical"
|
||||||
os: "archlinux"
|
os: "archlinux"
|
||||||
@@ -21,10 +18,3 @@ all:
|
|||||||
disks:
|
disks:
|
||||||
- device: "/dev/sda"
|
- device: "/dev/sda"
|
||||||
size: 120
|
size: 120
|
||||||
users:
|
|
||||||
admin:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
keys:
|
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
|
||||||
password: "CHANGE_ME"
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ all:
|
|||||||
url: "pve01.example.com"
|
url: "pve01.example.com"
|
||||||
username: "root@pam"
|
username: "root@pam"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
node: "pve01"
|
host: "pve01"
|
||||||
storage: "local-lvm"
|
storage: "local-lvm"
|
||||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
boot_iso: "local:iso/archlinux-x86_64.iso"
|
||||||
children:
|
children:
|
||||||
@@ -23,17 +23,16 @@ all:
|
|||||||
cpus: 2
|
cpus: 2
|
||||||
memory: 4096
|
memory: 4096
|
||||||
balloon: 0
|
balloon: 0
|
||||||
network:
|
network: "vmbr0"
|
||||||
bridge: "vmbr0"
|
ip: 10.0.0.10
|
||||||
ip: 10.0.0.10
|
prefix: 24
|
||||||
prefix: 24
|
gateway: 10.0.0.1
|
||||||
gateway: 10.0.0.1
|
dns:
|
||||||
dns:
|
servers:
|
||||||
servers:
|
- 1.1.1.1
|
||||||
- 1.1.1.1
|
- 1.0.0.1
|
||||||
- 1.0.0.1
|
search:
|
||||||
search:
|
- example.com
|
||||||
- example.com
|
|
||||||
disks:
|
disks:
|
||||||
- size: 40
|
- size: 40
|
||||||
- size: 80
|
- size: 80
|
||||||
@@ -42,11 +41,10 @@ all:
|
|||||||
fstype: xfs
|
fstype: xfs
|
||||||
label: DATA
|
label: DATA
|
||||||
opts: defaults
|
opts: defaults
|
||||||
users:
|
user:
|
||||||
ops:
|
name: "ops"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
key: "ssh-ed25519 AAAA..."
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
packages:
|
packages:
|
||||||
@@ -84,26 +82,22 @@ all:
|
|||||||
id: 101
|
id: 101
|
||||||
cpus: 4
|
cpus: 4
|
||||||
memory: 8192
|
memory: 8192
|
||||||
network:
|
network: "vmbr0"
|
||||||
bridge: "vmbr0"
|
ip: 10.0.0.11
|
||||||
ip: 10.0.0.11
|
prefix: 24
|
||||||
prefix: 24
|
gateway: 10.0.0.1
|
||||||
gateway: 10.0.0.1
|
dns:
|
||||||
dns:
|
servers: "1.1.1.1,1.0.0.1"
|
||||||
servers:
|
|
||||||
- "1.1.1.1"
|
|
||||||
- "1.0.0.1"
|
|
||||||
disks:
|
disks:
|
||||||
- size: 80
|
- size: 80
|
||||||
- size: 200
|
- size: 200
|
||||||
mount:
|
mount:
|
||||||
path: /srv/data
|
path: /srv/data
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
users:
|
user:
|
||||||
dbadmin:
|
name: "dbadmin"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
key: "ssh-ed25519 AAAA..."
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ all:
|
|||||||
url: "localhost"
|
url: "localhost"
|
||||||
username: ""
|
username: ""
|
||||||
password: ""
|
password: ""
|
||||||
|
host: ""
|
||||||
storage: "default"
|
storage: "default"
|
||||||
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
|
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
|
||||||
children:
|
children:
|
||||||
@@ -21,16 +22,15 @@ all:
|
|||||||
name: "web01.local"
|
name: "web01.local"
|
||||||
cpus: 2
|
cpus: 2
|
||||||
memory: 2048
|
memory: 2048
|
||||||
network:
|
network: "default"
|
||||||
bridge: "default"
|
ip: 192.168.122.20
|
||||||
ip: 192.168.122.20
|
prefix: 24
|
||||||
prefix: 24
|
gateway: 192.168.122.1
|
||||||
gateway: 192.168.122.1
|
dns:
|
||||||
dns:
|
servers:
|
||||||
servers:
|
- 1.1.1.1
|
||||||
- 1.1.1.1
|
search:
|
||||||
search:
|
- lab.local
|
||||||
- lab.local
|
|
||||||
path: "/var/lib/libvirt/images"
|
path: "/var/lib/libvirt/images"
|
||||||
disks:
|
disks:
|
||||||
- size: 30
|
- size: 30
|
||||||
@@ -38,11 +38,10 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /var/www
|
path: /var/www
|
||||||
fstype: xfs
|
fstype: xfs
|
||||||
users:
|
user:
|
||||||
web:
|
name: "web"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
key: "ssh-ed25519 AAAA..."
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
packages:
|
packages:
|
||||||
@@ -64,27 +63,25 @@ all:
|
|||||||
name: "db01.local"
|
name: "db01.local"
|
||||||
cpus: 4
|
cpus: 4
|
||||||
memory: 4096
|
memory: 4096
|
||||||
network:
|
network: "default"
|
||||||
bridge: "default"
|
ip: 192.168.122.21
|
||||||
ip: 192.168.122.21
|
prefix: 24
|
||||||
prefix: 24
|
gateway: 192.168.122.1
|
||||||
gateway: 192.168.122.1
|
dns:
|
||||||
dns:
|
servers:
|
||||||
servers:
|
- 9.9.9.9
|
||||||
- 9.9.9.9
|
search:
|
||||||
search:
|
- example.com
|
||||||
- example.com
|
|
||||||
disks:
|
disks:
|
||||||
- size: 60
|
- size: 60
|
||||||
- size: 120
|
- size: 120
|
||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
users:
|
user:
|
||||||
db:
|
name: "db"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
key: "ssh-ed25519 AAAA..."
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
@@ -106,26 +103,22 @@ all:
|
|||||||
name: "compute01.local"
|
name: "compute01.local"
|
||||||
cpus: 8
|
cpus: 8
|
||||||
memory: 8192
|
memory: 8192
|
||||||
network:
|
network: "default"
|
||||||
bridge: "default"
|
ip: 192.168.122.22
|
||||||
ip: 192.168.122.22
|
prefix: 24
|
||||||
prefix: 24
|
gateway: 192.168.122.1
|
||||||
gateway: 192.168.122.1
|
dns:
|
||||||
dns:
|
servers: "1.1.1.1,1.0.0.1"
|
||||||
servers:
|
|
||||||
- "1.1.1.1"
|
|
||||||
- "1.0.0.1"
|
|
||||||
disks:
|
disks:
|
||||||
- size: 80
|
- size: 80
|
||||||
- size: 200
|
- size: 200
|
||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: btrfs
|
fstype: btrfs
|
||||||
users:
|
user:
|
||||||
compute:
|
name: "compute"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
key: "ssh-ed25519 AAAA..."
|
||||||
- "ssh-ed25519 AAAA..."
|
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
features:
|
features:
|
||||||
|
|||||||
211
main.yml
211
main.yml
@@ -1,10 +1,67 @@
|
|||||||
---
|
---
|
||||||
- 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: Apply prompted authentication values to system input
|
||||||
|
vars:
|
||||||
|
system_input: "{{ system | default({}) }}"
|
||||||
|
system_user_input: "{{ (system_input.user | default({})) if (system_input.user is mapping) else {} }}"
|
||||||
|
system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}"
|
||||||
|
prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}"
|
||||||
|
prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string }}"
|
||||||
|
prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
|
||||||
|
prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system: >-
|
||||||
|
{{
|
||||||
|
system_input
|
||||||
|
| combine(
|
||||||
|
{
|
||||||
|
'user': {
|
||||||
|
'name': (
|
||||||
|
(system_user_input.name | default('') | string | length) > 0
|
||||||
|
) | ternary(system_user_input.name | string, prompt_user_name),
|
||||||
|
'key': (
|
||||||
|
(system_user_input.key | default('') | string | length) > 0
|
||||||
|
) | ternary(system_user_input.key | string, prompt_user_key),
|
||||||
|
'password': (
|
||||||
|
(system_user_input.password | default('') | string | length) > 0
|
||||||
|
) | ternary(system_user_input.password | string, prompt_user_password)
|
||||||
|
},
|
||||||
|
'root': {
|
||||||
|
'password': (
|
||||||
|
(system_root_input.password | default('') | string | length) > 0
|
||||||
|
) | ternary(system_root_input.password | string, prompt_root_password)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
recursive=True
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Load global defaults
|
- name: Load global defaults
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: global_defaults
|
name: global_defaults
|
||||||
@@ -13,89 +70,32 @@
|
|||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: system_check
|
name: system_check
|
||||||
|
|
||||||
tasks:
|
roles:
|
||||||
- name: Bootstrap pipeline
|
- role: virtualization
|
||||||
block:
|
when: system_cfg.type == "virtual"
|
||||||
- name: Record that no pre-existing VM was found
|
become: false
|
||||||
ansible.builtin.set_fact:
|
vars:
|
||||||
_vm_absent_before_bootstrap: true
|
ansible_connection: local
|
||||||
|
|
||||||
- name: Create virtual machine
|
- role: environment
|
||||||
when: system_cfg.type == "virtual"
|
vars:
|
||||||
ansible.builtin.include_role:
|
ansible_connection: "{{ 'vmware_tools' if hypervisor_type == 'vmware' else 'ssh' }}"
|
||||||
name: virtualization
|
|
||||||
public: true
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_become: false
|
|
||||||
|
|
||||||
- name: Configure environment
|
- role: partitioning
|
||||||
ansible.builtin.include_role:
|
vars:
|
||||||
name: environment
|
partitioning_boot_partition_suffix: 1
|
||||||
public: true
|
partitioning_main_partition_suffix: 2
|
||||||
|
|
||||||
- name: Partition disks
|
- role: bootstrap
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: partitioning
|
|
||||||
public: true
|
|
||||||
vars:
|
|
||||||
partitioning_boot_partition_suffix: 1
|
|
||||||
partitioning_main_partition_suffix: 2
|
|
||||||
|
|
||||||
- name: Install base system
|
- role: configuration
|
||||||
ansible.builtin.include_role:
|
|
||||||
name: bootstrap
|
|
||||||
public: true
|
|
||||||
|
|
||||||
- name: Apply system configuration
|
- role: cis
|
||||||
ansible.builtin.include_role:
|
when: system_cfg.features.cis.enabled | bool
|
||||||
name: configuration
|
|
||||||
public: true
|
|
||||||
|
|
||||||
# Past this point the OS is installed and configured; a CIS hardening or
|
- role: cleanup
|
||||||
# cleanup failure must not delete an otherwise-good VM.
|
when: system_cfg.type in ["virtual", "physical"]
|
||||||
- name: Mark base system complete
|
become: false
|
||||||
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,67 +103,22 @@
|
|||||||
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 ((system_cfg.ip | default('') | string | length) > 0)
|
||||||
or (
|
or (
|
||||||
system_cfg.type == 'physical'
|
system_cfg.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: "{{ system_cfg.user.name }}"
|
||||||
ansible_host: "{{ system_cfg.network.ip }}"
|
ansible_password: "{{ system_cfg.user.password }}"
|
||||||
ansible_port: 22
|
ansible_become_password: "{{ system_cfg.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
|
|
||||||
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:
|
|
||||||
firewall_phase: postreboot
|
|
||||||
|
|
||||||
- name: Install post-reboot packages
|
- name: Install post-reboot packages
|
||||||
when:
|
when:
|
||||||
|
|||||||
@@ -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
|
|
||||||
35
roles/bootstrap/tasks/almalinux.yml
Normal file
35
roles/bootstrap/tasks/almalinux.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap AlmaLinux
|
||||||
|
vars:
|
||||||
|
bootstrap_almalinux_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Install AlmaLinux base system
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
dnf --releasever={{ os_version }} --best --repo=baseos --repo=appstream
|
||||||
|
--installroot=/mnt --setopt=install_weak_deps=False
|
||||||
|
groupinstall -y core
|
||||||
|
register: bootstrap_almalinux_base_result
|
||||||
|
changed_when: bootstrap_almalinux_base_result.rc == 0
|
||||||
|
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
|
||||||
|
- name: Install extra packages
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_almalinux_extra }}
|
||||||
|
register: bootstrap_almalinux_extra_result
|
||||||
|
changed_when: bootstrap_almalinux_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Reinstall kernel core
|
||||||
|
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
|
||||||
|
register: bootstrap_almalinux_kernel_result
|
||||||
|
changed_when: bootstrap_almalinux_kernel_result.rc == 0
|
||||||
33
roles/bootstrap/tasks/alpine.yml
Normal file
33
roles/bootstrap/tasks/alpine.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap Alpine Linux
|
||||||
|
vars:
|
||||||
|
bootstrap_alpine_packages: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', 'bootstrap_alpine') | reject('equalto', '') | join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Install Alpine Linux packages
|
||||||
|
ansible.builtin.command: >
|
||||||
|
apk --root /mnt --no-cache add alpine-base
|
||||||
|
register: bootstrap_alpine_bootstrap_result
|
||||||
|
changed_when: bootstrap_alpine_bootstrap_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install extra packages
|
||||||
|
when: bootstrap_alpine_packages | length > 0
|
||||||
|
ansible.builtin.command: >
|
||||||
|
apk --root /mnt add {{ bootstrap_alpine_packages }}
|
||||||
|
register: bootstrap_alpine_extra_result
|
||||||
|
changed_when: bootstrap_alpine_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install bootloader
|
||||||
|
ansible.builtin.command: >
|
||||||
|
apk --root /mnt add grub grub-efi efibootmgr
|
||||||
|
register: bootstrap_alpine_bootloader_result
|
||||||
|
changed_when: bootstrap_alpine_bootloader_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,65 @@
|
|||||||
vars:
|
vars:
|
||||||
bootstrap_debian_release: >-
|
bootstrap_debian_release: >-
|
||||||
{{
|
{{
|
||||||
'bookworm' if (os_version | string) == '12'
|
'buster' if (os_version | string) == '10'
|
||||||
|
else 'bullseye' if (os_version | string) == '11'
|
||||||
|
else 'bookworm' if (os_version | string) == '12'
|
||||||
else 'trixie' if (os_version | string) == '13'
|
else 'trixie' if (os_version | string) == '13'
|
||||||
else 'sid' if (os_version | string) == 'unstable'
|
else 'sid' if (os_version | string) == 'unstable'
|
||||||
else 'trixie'
|
else 'trixie'
|
||||||
}}
|
}}
|
||||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
bootstrap_debian_package_config: >-
|
||||||
bootstrap_debian_base_csv: "{{ (['ca-certificates'] + _config.base) | unique | join(',') }}"
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
}}
|
||||||
|
bootstrap_debian_base_packages: >-
|
||||||
|
{{
|
||||||
|
bootstrap_debian_package_config.base
|
||||||
|
| default([])
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
bootstrap_debian_extra_packages: >-
|
||||||
|
{{
|
||||||
|
bootstrap_debian_package_config.extra
|
||||||
|
| default([])
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
bootstrap_debian_base_csv: "{{ bootstrap_debian_base_packages | join(',') }}"
|
||||||
bootstrap_debian_extra_args: >-
|
bootstrap_debian_extra_args: >-
|
||||||
{{
|
{{
|
||||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
bootstrap_debian_extra_packages
|
||||||
| reject('equalto', '')
|
|
||||||
| join(' ')
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
block:
|
block:
|
||||||
- name: Validate Debian package configuration
|
- name: Validate Debian package configuration
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- _config is mapping
|
- bootstrap_debian_package_config is mapping
|
||||||
- _config.base is sequence
|
- bootstrap_debian_package_config.base is defined
|
||||||
- _config.extra is sequence
|
- bootstrap_debian_package_config.base is sequence
|
||||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
- bootstrap_debian_package_config.base is not string
|
||||||
|
- bootstrap_debian_package_config.extra is defined
|
||||||
|
- bootstrap_debian_package_config.extra is sequence
|
||||||
|
- bootstrap_debian_package_config.extra is not string
|
||||||
|
fail_msg: "bootstrap package definition for {{ bootstrap_var_key }} must be a mapping with base/extra lists."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- 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
|
- name: Install Debian base system
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
debootstrap --keyring=/usr/share/keyrings/debian-archive-keyring.gpg
|
debootstrap --include={{ bootstrap_debian_base_csv }}
|
||||||
--include={{ bootstrap_debian_base_csv }}
|
{{ bootstrap_debian_release }} /mnt http://deb.debian.org/debian/
|
||||||
{{ 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
|
register: bootstrap_debian_base_result
|
||||||
changed_when: bootstrap_debian_base_result.rc == 0
|
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
|
- name: Install extra packages
|
||||||
when: bootstrap_debian_extra_args | trim | length > 0
|
when: bootstrap_debian_extra_packages | length > 0
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
||||||
register: bootstrap_debian_extra_result
|
register: bootstrap_debian_extra_result
|
||||||
changed_when: bootstrap_debian_extra_result.rc == 0
|
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
|
- 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"
|
ansible.builtin.command: "{{ chroot_command }} apt remove -y libcups2 libavahi-common3 libavahi-common-data"
|
||||||
register: bootstrap_debian_remove_result
|
register: bootstrap_debian_remove_result
|
||||||
changed_when: bootstrap_debian_remove_result.rc == 0
|
changed_when: bootstrap_debian_remove_result.rc == 0
|
||||||
|
|||||||
35
roles/bootstrap/tasks/fedora.yml
Normal file
35
roles/bootstrap/tasks/fedora.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap Fedora
|
||||||
|
vars:
|
||||||
|
bootstrap_fedora_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Install Fedora base system
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
dnf --releasever={{ os_version }} --best --repo=fedora --repo=fedora-updates
|
||||||
|
--installroot=/mnt --setopt=install_weak_deps=False
|
||||||
|
groupinstall -y critical-path-base core
|
||||||
|
register: bootstrap_fedora_base_result
|
||||||
|
changed_when: bootstrap_fedora_base_result.rc == 0
|
||||||
|
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
|
||||||
|
- name: Install extra packages
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_fedora_extra }}
|
||||||
|
register: bootstrap_fedora_extra_result
|
||||||
|
changed_when: bootstrap_fedora_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Reinstall kernel core
|
||||||
|
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
|
||||||
|
register: bootstrap_fedora_kernel_result
|
||||||
|
changed_when: bootstrap_fedora_kernel_result.rc == 0
|
||||||
@@ -1,77 +1,45 @@
|
|||||||
---
|
---
|
||||||
- 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_resolved | default(os)) | lower }}"
|
||||||
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
bootstrap_var_key: "{{ 'bootstrap_' + ((os_resolved | default(os)) | lower | replace('-', '_')) }}"
|
||||||
|
block:
|
||||||
|
- name: Include AlmaLinux bootstrap tasks
|
||||||
|
when: bootstrap_os_key in ['almalinux', 'almalinux8', 'almalinux9', 'almalinux10']
|
||||||
|
ansible.builtin.include_tasks: almalinux.yml
|
||||||
|
|
||||||
# dnf --installroot never runs anaconda, so no authselect profile is selected and
|
- name: Include Alpine bootstrap tasks
|
||||||
# /etc/pam.d/system-auth is missing, leaving the system unable to authenticate.
|
when: bootstrap_os_key == 'alpine'
|
||||||
# local is the right profile: local-auth only, no pam_sss.so, still CIS-capable.
|
ansible.builtin.include_tasks: alpine.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 ArchLinux bootstrap tasks
|
||||||
when: >-
|
when: bootstrap_os_key == 'archlinux'
|
||||||
(system_cfg.features.firmware.enabled | bool)
|
ansible.builtin.include_tasks: archlinux.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 Debian bootstrap tasks
|
||||||
when: system_cfg.features.desktop.enabled | bool
|
when: bootstrap_os_key in ['debian10', 'debian11', 'debian12', 'debian13', 'debianunstable']
|
||||||
ansible.builtin.include_tasks: _desktop.yml
|
ansible.builtin.include_tasks: debian.yml
|
||||||
|
|
||||||
- name: Ensure chroot uses live environment DNS
|
- name: Include Fedora bootstrap tasks
|
||||||
ansible.builtin.file:
|
when: bootstrap_os_key in ['fedora', 'fedora40', 'fedora41', 'fedora42', 'fedora43']
|
||||||
src: /run/NetworkManager/resolv.conf
|
ansible.builtin.include_tasks: fedora.yml
|
||||||
dest: /mnt/etc/resolv.conf
|
|
||||||
state: link
|
- name: Include openSUSE bootstrap tasks
|
||||||
force: true
|
when: bootstrap_os_key == 'opensuse'
|
||||||
|
ansible.builtin.include_tasks: opensuse.yml
|
||||||
|
|
||||||
|
- name: Include Rocky bootstrap tasks
|
||||||
|
when: bootstrap_os_key in ['rocky', 'rocky8', 'rocky9', 'rocky10']
|
||||||
|
ansible.builtin.include_tasks: rocky.yml
|
||||||
|
|
||||||
|
- name: Include RHEL bootstrap tasks
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Include Void bootstrap tasks
|
||||||
|
when: bootstrap_os_key == 'void'
|
||||||
|
ansible.builtin.include_tasks: void.yml
|
||||||
|
|||||||
33
roles/bootstrap/tasks/opensuse.yml
Normal file
33
roles/bootstrap/tasks/opensuse.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap openSUSE
|
||||||
|
vars:
|
||||||
|
bootstrap_opensuse_packages: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', 'bootstrap_opensuse') | reject('equalto', '') | join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Install openSUSE base packages
|
||||||
|
ansible.builtin.command: >
|
||||||
|
zypper --root /mnt --non-interactive install -t pattern patterns-base-base
|
||||||
|
register: bootstrap_opensuse_base_result
|
||||||
|
changed_when: bootstrap_opensuse_base_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install openSUSE extra packages
|
||||||
|
when: bootstrap_opensuse_packages | length > 0
|
||||||
|
ansible.builtin.command: >
|
||||||
|
zypper --root /mnt --non-interactive install {{ bootstrap_opensuse_packages }}
|
||||||
|
register: bootstrap_opensuse_extra_result
|
||||||
|
changed_when: bootstrap_opensuse_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install bootloader
|
||||||
|
ansible.builtin.command: >
|
||||||
|
zypper --root /mnt --non-interactive install grub2 grub2-efi efibootmgr
|
||||||
|
register: bootstrap_opensuse_bootloader_result
|
||||||
|
changed_when: bootstrap_opensuse_bootloader_result.rc == 0
|
||||||
@@ -1,43 +1,36 @@
|
|||||||
---
|
---
|
||||||
- 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
|
||||||
|
|
||||||
- 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: "{{ chroot_command }} rpm --rebuilddb"
|
||||||
@@ -46,14 +39,22 @@
|
|||||||
|
|
||||||
- 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
|
{{ chroot_command }} 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
|
||||||
|
|||||||
35
roles/bootstrap/tasks/rocky.yml
Normal file
35
roles/bootstrap/tasks/rocky.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap Rocky Linux
|
||||||
|
vars:
|
||||||
|
bootstrap_rocky_extra: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', bootstrap_var_key)
|
||||||
|
| reject('equalto', '')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Install Rocky Linux base system
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
dnf --releasever={{ os_version }} --best --repo=baseos --repo=appstream
|
||||||
|
--installroot=/mnt --setopt=install_weak_deps=False
|
||||||
|
groupinstall -y core
|
||||||
|
register: bootstrap_rocky_base_result
|
||||||
|
changed_when: bootstrap_rocky_base_result.rc == 0
|
||||||
|
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
|
||||||
|
- name: Install extra packages
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
|
||||||
|
install -y {{ bootstrap_rocky_extra }}
|
||||||
|
register: bootstrap_rocky_extra_result
|
||||||
|
changed_when: bootstrap_rocky_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Reinstall kernel core
|
||||||
|
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
|
||||||
|
register: bootstrap_rocky_kernel_result
|
||||||
|
changed_when: bootstrap_rocky_kernel_result.rc == 0
|
||||||
@@ -1,85 +1,68 @@
|
|||||||
---
|
---
|
||||||
- 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_package_config: >-
|
||||||
ubuntu-lts: resolute
|
|
||||||
bootstrap_ubuntu_release: "{{ bootstrap_ubuntu_release_map[os] | default('resolute') }}"
|
|
||||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
|
||||||
bootstrap_ubuntu_base_csv: "{{ (['ca-certificates'] + _config.base) | unique | join(',') }}"
|
|
||||||
bootstrap_ubuntu_extra_args: >-
|
|
||||||
{{
|
{{
|
||||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
lookup('vars', bootstrap_var_key)
|
||||||
| reject('equalto', '')
|
|
||||||
| join(' ')
|
|
||||||
}}
|
}}
|
||||||
|
bootstrap_ubuntu_base_packages: >-
|
||||||
|
{{
|
||||||
|
bootstrap_ubuntu_package_config.base
|
||||||
|
| default([])
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
bootstrap_ubuntu_extra_packages: >-
|
||||||
|
{{
|
||||||
|
bootstrap_ubuntu_package_config.extra
|
||||||
|
| default([])
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
bootstrap_ubuntu_base_csv: "{{ bootstrap_ubuntu_base_packages | join(',') }}"
|
||||||
|
bootstrap_ubuntu_extra: "{{ bootstrap_ubuntu_extra_packages | join(' ') }}"
|
||||||
block:
|
block:
|
||||||
- name: Validate Ubuntu package configuration
|
- name: Validate Ubuntu package configuration
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- _config is mapping
|
- bootstrap_ubuntu_package_config is mapping
|
||||||
- _config.base is sequence
|
- bootstrap_ubuntu_package_config.base is defined
|
||||||
- _config.extra is sequence
|
- bootstrap_ubuntu_package_config.base is sequence
|
||||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
- bootstrap_ubuntu_package_config.base is not string
|
||||||
|
- bootstrap_ubuntu_package_config.extra is defined
|
||||||
|
- bootstrap_ubuntu_package_config.extra is sequence
|
||||||
|
- bootstrap_ubuntu_package_config.extra is not string
|
||||||
|
fail_msg: "bootstrap package definition for {{ bootstrap_var_key }} must be a mapping with base/extra lists."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Check for a debootstrap script for the target release
|
|
||||||
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
|
- name: Install Ubuntu base system
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
debootstrap
|
debootstrap --include={{ bootstrap_ubuntu_base_csv }}
|
||||||
--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg
|
|
||||||
--include={{ bootstrap_ubuntu_base_csv }}
|
|
||||||
{{ bootstrap_ubuntu_release }} /mnt
|
{{ bootstrap_ubuntu_release }} /mnt
|
||||||
{{ system_cfg.content.url }}
|
http://archive.ubuntu.com/ubuntu/
|
||||||
environment:
|
|
||||||
http_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
https_proxy: "{{ system_cfg.content.proxy }}"
|
|
||||||
register: bootstrap_ubuntu_base_result
|
register: bootstrap_ubuntu_base_result
|
||||||
changed_when: bootstrap_ubuntu_base_result.rc == 0
|
changed_when: bootstrap_ubuntu_base_result.rc == 0
|
||||||
|
|
||||||
- name: Write bootstrap sources.list
|
- name: Ensure chroot has resolv.conf
|
||||||
ansible.builtin.template:
|
ansible.builtin.file:
|
||||||
src: ubuntu.sources.list.j2
|
src: /run/NetworkManager/resolv.conf
|
||||||
dest: /mnt/etc/apt/sources.list
|
dest: /mnt/etc/resolv.conf
|
||||||
mode: "0644"
|
state: link
|
||||||
|
|
||||||
- name: Configure apt performance tuning
|
- name: Enable universe repository
|
||||||
ansible.builtin.copy:
|
ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list"
|
||||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
register: bootstrap_ubuntu_repo_result
|
||||||
content: |
|
changed_when: bootstrap_ubuntu_repo_result.rc == 0
|
||||||
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
|
- name: Update package lists
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt update"
|
ansible.builtin.command: "{{ chroot_command }} apt update"
|
||||||
register: bootstrap_ubuntu_update_result
|
register: bootstrap_ubuntu_update_result
|
||||||
changed_when: bootstrap_ubuntu_update_result.rc == 0
|
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
|
- name: Install extra packages
|
||||||
when: bootstrap_ubuntu_extra_args | trim | length > 0
|
when: bootstrap_ubuntu_extra_packages | length > 0
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra_args }}"
|
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra }}"
|
||||||
register: bootstrap_ubuntu_extra_result
|
register: bootstrap_ubuntu_extra_result
|
||||||
changed_when: bootstrap_ubuntu_extra_result.rc == 0
|
changed_when: bootstrap_ubuntu_extra_result.rc == 0
|
||||||
|
|||||||
33
roles/bootstrap/tasks/void.yml
Normal file
33
roles/bootstrap/tasks/void.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Bootstrap Void Linux
|
||||||
|
vars:
|
||||||
|
bootstrap_void_packages: >-
|
||||||
|
{{
|
||||||
|
lookup('vars', 'bootstrap_void') | reject('equalto', '') | join(' ')
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Ensure chroot has resolv.conf
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Install Void Linux base packages
|
||||||
|
ansible.builtin.command: >
|
||||||
|
xbps-install -Sy -r /mnt -R https://repo-default.voidlinux.org/current void-repo-nonfree base-system
|
||||||
|
register: bootstrap_void_base_result
|
||||||
|
changed_when: bootstrap_void_base_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install extra packages
|
||||||
|
when: bootstrap_void_packages | length > 0
|
||||||
|
ansible.builtin.command: >
|
||||||
|
xbps-install -Su -r /mnt {{ bootstrap_void_packages }}
|
||||||
|
register: bootstrap_void_extra_result
|
||||||
|
changed_when: bootstrap_void_extra_result.rc == 0
|
||||||
|
|
||||||
|
- name: Install bootloader
|
||||||
|
ansible.builtin.command: >
|
||||||
|
xbps-install -Sy -r /mnt grub-x86_64-efi efibootmgr
|
||||||
|
register: bootstrap_void_bootloader_result
|
||||||
|
changed_when: bootstrap_void_bootloader_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,339 +1,267 @@
|
|||||||
---
|
---
|
||||||
# Feature-gated packages shared across all distros. Arch strips nftables from
|
bootstrap_rhel_base:
|
||||||
# this and composes it differently.
|
- bind-utils
|
||||||
bootstrap_common_conditional: >-
|
- dhcp-client
|
||||||
{{
|
- efibootmgr
|
||||||
(
|
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||||
(['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 '' }}"
|
||||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' 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' else '' }}"
|
||||||
+ (['nftables'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
- glibc-langpack-de
|
||||||
+ (['cryptsetup', 'tpm2-tools'] if system_cfg.luks.enabled | bool else [])
|
- glibc-langpack-en
|
||||||
+ (['qemu-guest-agent'] if hypervisor_type in ['libvirt', 'proxmox'] else [])
|
- lrzsz
|
||||||
+ (['open-vm-tools'] if hypervisor_type == 'vmware' else [])
|
- lvm2
|
||||||
+ (['cloud-init'] if system_cfg.features.cloud_init | bool else [])
|
- mtr
|
||||||
)
|
- ncurses-term
|
||||||
}}
|
- nfs-utils
|
||||||
|
- policycoreutils-python-utils
|
||||||
|
- shim
|
||||||
|
- tmux
|
||||||
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- zstd
|
||||||
|
|
||||||
# Native-installer parity backfill: anaconda and the d-i "standard" task leave
|
bootstrap_rhel_versioned:
|
||||||
# these, but install_weak_deps=False / Recommends-off minimal installs drop them.
|
- grub2
|
||||||
bootstrap_el_runtime:
|
- "{{ 'grub2-efi-x64' if os_version_major | default('') == '8' else 'grub2-efi' }}"
|
||||||
- NetworkManager
|
- "{{ 'grub2-tools-extra' if os_version_major | default('') in ['8', '9'] else '' }}"
|
||||||
- authselect
|
- "{{ 'python39' if os_version_major | default('') == '8' else 'python' }}"
|
||||||
- authselect-libs
|
- "{{ 'kernel' if os_version_major | default('') == '10' else '' }}"
|
||||||
- chrony
|
- "{{ 'zram-generator' if os_version_major | default('') in ['9', '10'] else '' }}"
|
||||||
- crypto-policies
|
|
||||||
- crypto-policies-scripts
|
|
||||||
- dbus
|
|
||||||
- polkit
|
|
||||||
|
|
||||||
bootstrap_deb_runtime:
|
bootstrap_rhel_common: "{{ bootstrap_rhel_base + bootstrap_rhel_versioned }}"
|
||||||
- apparmor-utils
|
|
||||||
- chrony
|
|
||||||
- libpam-pwquality
|
|
||||||
- needrestart
|
|
||||||
- network-manager
|
|
||||||
- sudo
|
|
||||||
|
|
||||||
# Per-OS package definitions: base (rootfs/group install), extra (post-base),
|
bootstrap_rhel8: "{{ bootstrap_rhel_common }}"
|
||||||
# conditional (feature/version-gated, appended by task files). DNF distros also
|
bootstrap_rhel9: "{{ bootstrap_rhel_common }}"
|
||||||
# carry repos and use base as group names.
|
bootstrap_rhel10: "{{ bootstrap_rhel_common }}"
|
||||||
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:
|
"{{ bootstrap_rhel_base + ['grub2', 'grub2-efi', 'dbus-daemon', 'lrzsz', 'nfsv4-client-utils', 'nc', 'ppp', 'zram-generator'] }}"
|
||||||
- baseos
|
|
||||||
- appstream
|
|
||||||
base:
|
|
||||||
- core
|
|
||||||
extra:
|
|
||||||
- bind-utils
|
|
||||||
- efibootmgr
|
|
||||||
- glibc-langpack-de
|
|
||||||
- glibc-langpack-en
|
|
||||||
- grub2
|
|
||||||
- grub2-efi
|
|
||||||
- kernel
|
|
||||||
- lrzsz
|
|
||||||
- lvm2
|
|
||||||
- mtr
|
|
||||||
- nc
|
|
||||||
- ncurses-term
|
|
||||||
- nfs-utils
|
|
||||||
- nfsv4-client-utils
|
|
||||||
- policycoreutils-python-utils
|
|
||||||
- ppp
|
|
||||||
- python3
|
|
||||||
- shim
|
|
||||||
- tmux
|
|
||||||
- vim
|
|
||||||
- zram-generator
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['dhcp-client'] if (os_version_major | default('10') | int) < 10 else [])
|
|
||||||
+ bootstrap_el_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_rocky:
|
bootstrap_rocky:
|
||||||
repos:
|
"{{ bootstrap_rhel_base + ['grub2', 'grub2-efi', 'nfsv4-client-utils', 'nc', 'ppp', 'telnet', 'util-linux-core', 'wget', 'zram-generator'] }}"
|
||||||
- baseos
|
|
||||||
- appstream
|
bootstrap_almalinux8: "{{ bootstrap_almalinux }}"
|
||||||
base:
|
bootstrap_almalinux9: "{{ bootstrap_almalinux }}"
|
||||||
- core
|
bootstrap_almalinux10: "{{ bootstrap_almalinux }}"
|
||||||
extra:
|
|
||||||
- bind-utils
|
bootstrap_rocky8: "{{ bootstrap_rocky }}"
|
||||||
- efibootmgr
|
bootstrap_rocky9: "{{ bootstrap_rocky }}"
|
||||||
- glibc-langpack-de
|
bootstrap_rocky10: "{{ bootstrap_rocky }}"
|
||||||
- glibc-langpack-en
|
|
||||||
- grub2
|
|
||||||
- grub2-efi
|
|
||||||
- kernel
|
|
||||||
- lrzsz
|
|
||||||
- lvm2
|
|
||||||
- mtr
|
|
||||||
- nc
|
|
||||||
- ncurses-term
|
|
||||||
- nfs-utils
|
|
||||||
- nfsv4-client-utils
|
|
||||||
- policycoreutils-python-utils
|
|
||||||
- ppp
|
|
||||||
- python3
|
|
||||||
- shim
|
|
||||||
- telnet
|
|
||||||
- tmux
|
|
||||||
- util-linux-core
|
|
||||||
- vim
|
|
||||||
- wget
|
|
||||||
- zram-generator
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['dhcp-client'] if (os_version_major | default('9') | int) < 10 else [])
|
|
||||||
+ bootstrap_el_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_fedora:
|
bootstrap_fedora:
|
||||||
repos:
|
- bat
|
||||||
- fedora
|
- bind-utils
|
||||||
- fedora-updates
|
- btrfs-progs
|
||||||
base:
|
- cronie
|
||||||
- critical-path-base
|
- dhcp-client
|
||||||
- core
|
- duf
|
||||||
extra:
|
- efibootmgr
|
||||||
- bat
|
- entr
|
||||||
- bind-utils
|
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||||
- btrfs-progs
|
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||||
- cronie
|
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
|
||||||
- dhcp-client
|
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
- duf
|
- fish
|
||||||
- efibootmgr
|
- fzf
|
||||||
- entr
|
- glibc-langpack-de
|
||||||
- fish
|
- glibc-langpack-en
|
||||||
- fzf
|
- grub2
|
||||||
- glibc-langpack-de
|
- grub2-efi
|
||||||
- glibc-langpack-en
|
- htop
|
||||||
- grub2
|
- iperf3
|
||||||
- grub2-efi
|
- logrotate
|
||||||
- htop
|
- lrzsz
|
||||||
- iperf3
|
- lvm2
|
||||||
- logrotate
|
- nc
|
||||||
- lrzsz
|
- nfs-utils
|
||||||
- lvm2
|
- nfsv4-client-utils
|
||||||
- nc
|
- polkit
|
||||||
- nfs-utils
|
- ppp
|
||||||
- nfsv4-client-utils
|
- ripgrep
|
||||||
- ppp
|
- shim
|
||||||
- python3
|
- tmux
|
||||||
- ripgrep
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
- shim
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
- tmux
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
- vim-default-editor
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
- wget
|
- vim-default-editor
|
||||||
- zoxide
|
- wget
|
||||||
- zram-generator
|
- zoxide
|
||||||
- zstd
|
- zram-generator
|
||||||
conditional: "{{ bootstrap_el_runtime + bootstrap_common_conditional }}"
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_fedora40: "{{ bootstrap_fedora }}"
|
||||||
|
bootstrap_fedora41: "{{ bootstrap_fedora }}"
|
||||||
|
bootstrap_fedora42: "{{ bootstrap_fedora }}"
|
||||||
|
bootstrap_fedora43: "{{ bootstrap_fedora }}"
|
||||||
|
|
||||||
|
bootstrap_debian_base_common:
|
||||||
|
- btrfs-progs
|
||||||
|
- cron
|
||||||
|
- gnupg
|
||||||
|
- grub-efi
|
||||||
|
- grub-efi-amd64-signed
|
||||||
|
- grub2-common
|
||||||
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'cryptsetup-initramfs' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- locales
|
||||||
|
- logrotate
|
||||||
|
- lvm2
|
||||||
|
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
|
||||||
|
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
|
- "{{ 'openssh-server' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||||
|
- python3
|
||||||
|
- xfsprogs
|
||||||
|
|
||||||
|
bootstrap_debian_extra_common:
|
||||||
|
- apparmor-utils
|
||||||
|
- bat
|
||||||
|
- chrony
|
||||||
|
- curl
|
||||||
|
- entr
|
||||||
|
- "{{ '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 '' }}"
|
||||||
|
- fish
|
||||||
|
- fzf
|
||||||
|
- htop
|
||||||
|
- jq
|
||||||
|
- libpam-pwquality
|
||||||
|
- lrzsz
|
||||||
|
- mtr
|
||||||
|
- ncdu
|
||||||
|
- net-tools
|
||||||
|
- network-manager
|
||||||
|
- python-is-python3
|
||||||
|
- ripgrep
|
||||||
|
- rsync
|
||||||
|
- screen
|
||||||
|
- sudo
|
||||||
|
- syslog-ng
|
||||||
|
- tcpd
|
||||||
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
- zstd
|
||||||
|
|
||||||
|
bootstrap_debian_extra_versioned:
|
||||||
|
- linux-image-amd64
|
||||||
|
- "{{ 'duf' if (os_version | string) not in ['10', '11'] else '' }}"
|
||||||
|
- "{{ 'fastfetch' if (os_version | string) in ['12', '13', 'unstable'] else '' }}"
|
||||||
|
- "{{ 'neofetch' if (os_version | string) == '12' else '' }}"
|
||||||
|
- "{{ 'software-properties-common' if (os_version | string) not in ['13', 'unstable'] else '' }}"
|
||||||
|
- "{{ 'systemd-zram-generator' if (os_version | string) not in ['10', '11'] else '' }}"
|
||||||
|
- "{{ 'tldr' if (os_version | string) not in ['13', 'unstable'] else '' }}"
|
||||||
|
|
||||||
bootstrap_debian:
|
bootstrap_debian:
|
||||||
base:
|
base: "{{ bootstrap_debian_base_common }}"
|
||||||
- btrfs-progs
|
extra: "{{ bootstrap_debian_extra_common + bootstrap_debian_extra_versioned }}"
|
||||||
- cron
|
|
||||||
- cryptsetup-initramfs
|
bootstrap_debian10: "{{ bootstrap_debian }}"
|
||||||
- gnupg
|
bootstrap_debian11: "{{ bootstrap_debian }}"
|
||||||
- grub-efi
|
bootstrap_debian12: "{{ bootstrap_debian }}"
|
||||||
- grub-efi-amd64-signed
|
bootstrap_debian13: "{{ bootstrap_debian }}"
|
||||||
- grub2-common
|
bootstrap_debianunstable: "{{ bootstrap_debian }}"
|
||||||
- locales
|
|
||||||
- logrotate
|
|
||||||
- lvm2
|
|
||||||
- openssh-server
|
|
||||||
- python3
|
|
||||||
- xfsprogs
|
|
||||||
extra:
|
|
||||||
- bat
|
|
||||||
- curl
|
|
||||||
- entr
|
|
||||||
- fish
|
|
||||||
- fzf
|
|
||||||
- htop
|
|
||||||
- jq
|
|
||||||
- linux-image-amd64
|
|
||||||
- lrzsz
|
|
||||||
- mtr
|
|
||||||
- ncdu
|
|
||||||
- net-tools
|
|
||||||
- python-is-python3
|
|
||||||
- ripgrep
|
|
||||||
- rsync
|
|
||||||
- screen
|
|
||||||
- syslog-ng
|
|
||||||
- tcpd
|
|
||||||
- vim
|
|
||||||
- wget
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
|
||||||
(['duf'] if (os_version | string) not in ['10', '11'] else [])
|
|
||||||
+ (['fastfetch'] if (os_version | string) in ['13', 'unstable'] else [])
|
|
||||||
+ (['neofetch'] if (os_version | string) == '12' else [])
|
|
||||||
+ (['software-properties-common'] if (os_version | string) not in ['13', 'unstable'] else [])
|
|
||||||
+ (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else [])
|
|
||||||
+ (['tldr'] if (os_version | string) not in ['13', 'unstable'] else [])
|
|
||||||
+ (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
|
||||||
+ bootstrap_deb_runtime
|
|
||||||
+ bootstrap_common_conditional
|
|
||||||
}}
|
|
||||||
|
|
||||||
bootstrap_ubuntu:
|
bootstrap_ubuntu:
|
||||||
base:
|
base:
|
||||||
- btrfs-progs
|
|
||||||
- cron
|
|
||||||
- cryptsetup-initramfs
|
|
||||||
- gnupg
|
|
||||||
- grub-efi
|
|
||||||
- grub-efi-amd64-signed
|
|
||||||
- grub2-common
|
|
||||||
- initramfs-tools
|
|
||||||
- linux-image-generic
|
- linux-image-generic
|
||||||
- locales
|
extra: >-
|
||||||
- logrotate
|
|
||||||
- lvm2
|
|
||||||
- openssh-server
|
|
||||||
- python3
|
|
||||||
- xfsprogs
|
|
||||||
extra:
|
|
||||||
- bash-completion
|
|
||||||
- bat
|
|
||||||
- curl
|
|
||||||
- dnsutils
|
|
||||||
- duf
|
|
||||||
- entr
|
|
||||||
- eza
|
|
||||||
- fdupes
|
|
||||||
- fio
|
|
||||||
- fish
|
|
||||||
- fzf
|
|
||||||
- htop
|
|
||||||
- jq
|
|
||||||
- lrzsz
|
|
||||||
- mtr
|
|
||||||
- ncdu
|
|
||||||
- ncurses-term
|
|
||||||
- net-tools
|
|
||||||
- python-is-python3
|
|
||||||
- ripgrep
|
|
||||||
- rsync
|
|
||||||
- screen
|
|
||||||
- software-properties-common
|
|
||||||
- syslog-ng
|
|
||||||
- systemd-zram-generator
|
|
||||||
- tcpd
|
|
||||||
- traceroute
|
|
||||||
- util-linux-extra
|
|
||||||
- vim
|
|
||||||
- wget
|
|
||||||
- yq
|
|
||||||
- zoxide
|
|
||||||
- zstd
|
|
||||||
conditional: >-
|
|
||||||
{{
|
{{
|
||||||
(['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
bootstrap_debian_base_common
|
||||||
+ bootstrap_deb_runtime
|
+ bootstrap_debian_extra_common
|
||||||
+ bootstrap_common_conditional
|
+ ['bash-completion', 'dnsutils', 'duf', 'eza', 'fdupes', 'fio', 'ncurses-term', 'software-properties-common', 'systemd-zram-generator', 'tldr', 'traceroute', 'util-linux-extra', 'yq', 'zoxide']
|
||||||
|
}}
|
||||||
|
|
||||||
|
bootstrap_ubuntu_lts:
|
||||||
|
base:
|
||||||
|
- linux-image-generic
|
||||||
|
extra: >-
|
||||||
|
{{
|
||||||
|
bootstrap_debian_base_common
|
||||||
|
+ bootstrap_debian_extra_common
|
||||||
|
+ ['bash-completion', 'dnsutils', 'duf', 'eza', 'fdupes', 'fio', 'ncurses-term', 'software-properties-common', 'systemd-zram-generator', 'tldr', 'traceroute', 'util-linux-extra', 'yq', 'zoxide']
|
||||||
}}
|
}}
|
||||||
|
|
||||||
bootstrap_archlinux:
|
bootstrap_archlinux:
|
||||||
base:
|
- base
|
||||||
- base
|
- btrfs-progs
|
||||||
- btrfs-progs
|
- cronie
|
||||||
- cronie
|
- dhcpcd
|
||||||
- dhcpcd
|
- efibootmgr
|
||||||
- efibootmgr
|
- fastfetch
|
||||||
- fastfetch
|
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||||
- fish
|
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
|
||||||
- fzf
|
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
|
||||||
- grub
|
- "{{ 'iptables-nft' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
- htop
|
- fish
|
||||||
- libpwquality
|
- fzf
|
||||||
- linux
|
- grub
|
||||||
- logrotate
|
- htop
|
||||||
- lrzsz
|
- libpwquality
|
||||||
- lsof
|
- linux
|
||||||
- lvm2
|
- logrotate
|
||||||
- ncdu
|
- lrzsz
|
||||||
- networkmanager
|
- lsof
|
||||||
- nfs-utils
|
- lvm2
|
||||||
- ppp
|
- ncdu
|
||||||
- python
|
- networkmanager
|
||||||
- rsync
|
- nfs-utils
|
||||||
- sudo
|
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||||
- tldr
|
- ppp
|
||||||
- tmux
|
- prometheus-node-exporter
|
||||||
- vim
|
- python-psycopg2
|
||||||
- zram-generator
|
- reflector
|
||||||
extra: []
|
- rsync
|
||||||
conditional: >-
|
- sudo
|
||||||
{{
|
- tldr
|
||||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
- tmux
|
||||||
+ (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
+ (['sbctl'] if system_cfg.features.secure_boot.enabled | bool else [])
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
+ (['reflector'] if system_cfg.content.url | length == 0 else [])
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
+ (bootstrap_common_conditional | reject('equalto', 'nftables') | list)
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
}}
|
- vim
|
||||||
|
- wireguard-tools
|
||||||
|
- zram-generator
|
||||||
|
|
||||||
|
bootstrap_alpine:
|
||||||
|
- alpine-base
|
||||||
|
- vim
|
||||||
|
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
|
- "{{ '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' else '' }}"
|
||||||
|
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
|
|
||||||
|
bootstrap_opensuse:
|
||||||
|
- vim
|
||||||
|
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
|
- "{{ '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' else '' }}"
|
||||||
|
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
|
|
||||||
|
bootstrap_void:
|
||||||
|
- vim
|
||||||
|
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
|
||||||
|
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
|
||||||
|
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
|
||||||
|
- "{{ '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' else '' }}"
|
||||||
|
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
|
||||||
|
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
|
||||||
|
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
|
||||||
|
|||||||
@@ -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 != "rhel" else None,
|
||||||
|
{
|
||||||
|
"path": "/mnt/usr/bin/"
|
||||||
|
+ ("fusermount3" if os in ["archlinux", "fedora", "rocky"] or os == "rhel" or (os == "debian" and (os_version | string) == "12") else "fusermount"),
|
||||||
|
"mode": "755"
|
||||||
|
},
|
||||||
|
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian" and (os_version | string) == "11" 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 == "rhel" or os in ["almalinux", "rocky"]
|
||||||
_cis_crypto_policy: "{{ 'DEFAULT' if (os_version_major | int >= 10) else 'DEFAULT:NO-SHA1' }}"
|
ansible.builtin.command: "{{ chroot_command }} /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
|
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} 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 old 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 is_debian | bool
|
||||||
"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 is_debian | bool
|
||||||
# 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 is_debian | bool
|
||||||
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,9 @@
|
|||||||
---
|
---
|
||||||
# Post-reboot verification
|
cleanup_libvirt_image_dir: >-
|
||||||
cleanup_verify_boot: true
|
{{
|
||||||
cleanup_boot_timeout: 300
|
system_cfg.path
|
||||||
cleanup_remove_on_failure: true
|
if system_cfg is defined and (system_cfg.path | string | length) > 0
|
||||||
|
else '/var/lib/libvirt/images'
|
||||||
|
}}
|
||||||
|
cleanup_libvirt_cloudinit_path: >-
|
||||||
|
{{ [cleanup_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
|
||||||
|
|||||||
@@ -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,50 +85,19 @@
|
|||||||
|
|
||||||
- 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:
|
||||||
|
|||||||
@@ -3,33 +3,25 @@
|
|||||||
when: hypervisor_type == "proxmox"
|
when: hypervisor_type == "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_cfg.url }}"
|
||||||
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
|
api_password: "{{ hypervisor_cfg.password }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.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:
|
||||||
|
api_host: "{{ hypervisor_cfg.url }}"
|
||||||
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
|
api_password: "{{ hypervisor_cfg.password }}"
|
||||||
|
node: "{{ hypervisor_cfg.host }}"
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
state: stopped
|
state: restarted
|
||||||
force: true
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Boot the installed OS
|
|
||||||
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,7 +6,16 @@
|
|||||||
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: Cleanup vCenter resources
|
||||||
|
ansible.builtin.include_tasks: vmware.yml
|
||||||
|
|
||||||
|
- name: Cleanup libvirt resources
|
||||||
|
ansible.builtin.include_tasks: libvirt.yml
|
||||||
|
|
||||||
|
- name: Cleanup Xen resources
|
||||||
|
ansible.builtin.include_tasks: xen.yml
|
||||||
|
|
||||||
- name: Determine post-reboot connectivity
|
- name: Determine post-reboot connectivity
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -17,7 +26,7 @@
|
|||||||
if post_reboot_can_connect is defined
|
if post_reboot_can_connect is defined
|
||||||
else (
|
else (
|
||||||
(ansible_connection | default('ssh')) != 'ssh'
|
(ansible_connection | default('ssh')) != 'ssh'
|
||||||
or ((system_cfg.network.ip | default('') | string | length) > 0)
|
or ((system_cfg.ip | default('') | string | length) > 0)
|
||||||
or (
|
or (
|
||||||
system_cfg.type == 'physical'
|
system_cfg.type == 'physical'
|
||||||
and (ansible_host | default('') | string | length) > 0
|
and (ansible_host | default('') | string | length) > 0
|
||||||
@@ -25,27 +34,25 @@
|
|||||||
)
|
)
|
||||||
) | bool
|
) | bool
|
||||||
}}
|
}}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Check VM accessibility after reboot
|
- name: Check VM accessibility after reboot
|
||||||
when:
|
when:
|
||||||
- cleanup_verify_boot | bool
|
|
||||||
- system_cfg.type == "virtual"
|
- system_cfg.type == "virtual"
|
||||||
- cleanup_post_reboot_can_connect | bool
|
- cleanup_post_reboot_can_connect | bool
|
||||||
block:
|
block:
|
||||||
- name: Attempt to connect to VM
|
- name: Attempt to connect to VM
|
||||||
delegate_to: "{{ inventory_hostname }}"
|
delegate_to: "{{ inventory_hostname }}"
|
||||||
ansible.builtin.wait_for_connection:
|
ansible.builtin.wait_for_connection:
|
||||||
timeout: "{{ cleanup_boot_timeout }}"
|
timeout: 300
|
||||||
register: cleanup_vm_connection_check
|
register: cleanup_vm_connection_check
|
||||||
failed_when: false
|
failed_when: false
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: VM failed to boot - initiate cleanup
|
- name: VM failed to boot - initiate cleanup
|
||||||
when:
|
when:
|
||||||
- cleanup_remove_on_failure | bool
|
|
||||||
- cleanup_vm_connection_check is defined
|
- cleanup_vm_connection_check is defined
|
||||||
- cleanup_vm_connection_check.failed | bool
|
- cleanup_vm_connection_check.failed | bool
|
||||||
- virtualization_vm_created_in_run | default(false) | bool
|
|
||||||
block:
|
block:
|
||||||
- name: VM boot failure detected - removing VM
|
- name: VM boot failure detected - removing VM
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
@@ -54,103 +61,147 @@
|
|||||||
This VM was created in the current playbook run and will be removed
|
This VM was created in the current playbook run and will be removed
|
||||||
to prevent orphaned resources.
|
to prevent orphaned resources.
|
||||||
|
|
||||||
- name: Remove failed libvirt VM
|
- name: Remove VM for libvirt
|
||||||
when: hypervisor_type == "libvirt"
|
when:
|
||||||
|
- hypervisor_type == "libvirt"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
block:
|
community.libvirt.virt:
|
||||||
- name: Destroy libvirt VM
|
name: "{{ hostname }}"
|
||||||
community.libvirt.virt:
|
state: destroyed
|
||||||
name: "{{ hostname }}"
|
|
||||||
state: destroyed
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Undefine libvirt VM
|
- name: Undefine VM for libvirt
|
||||||
community.libvirt.virt:
|
when:
|
||||||
name: "{{ hostname }}"
|
- hypervisor_type == "libvirt"
|
||||||
command: undefine
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
|
||||||
- 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
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
module_defaults:
|
community.libvirt.virt:
|
||||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
name: "{{ hostname }}"
|
||||||
no_log: true
|
command: undefine
|
||||||
block:
|
|
||||||
- name: Stop Proxmox VM
|
|
||||||
community.proxmox.proxmox_kvm:
|
|
||||||
name: "{{ hostname }}"
|
|
||||||
vmid: "{{ system_cfg.id }}"
|
|
||||||
state: stopped
|
|
||||||
|
|
||||||
- name: Delete Proxmox VM
|
- name: Remove VM disk for libvirt
|
||||||
community.proxmox.proxmox_kvm:
|
when:
|
||||||
name: "{{ hostname }}"
|
- hypervisor_type == "libvirt"
|
||||||
vmid: "{{ system_cfg.id }}"
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
state: absent
|
|
||||||
unprivileged: false
|
|
||||||
|
|
||||||
- name: Remove failed VMware VM
|
|
||||||
when: hypervisor_type == "vmware"
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
module_defaults:
|
ansible.builtin.file:
|
||||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
path: "{{ item.path }}"
|
||||||
no_log: true
|
state: absent
|
||||||
block:
|
loop: "{{ virtualization_libvirt_disks | default([]) }}"
|
||||||
- name: Power off VMware VM
|
loop_control:
|
||||||
community.vmware.vmware_guest:
|
label: "{{ item.path }}"
|
||||||
name: "{{ hostname }}"
|
|
||||||
folder: "{{ system_cfg.path | default('/') }}"
|
|
||||||
state: poweredoff
|
|
||||||
|
|
||||||
- name: Delete VMware VM
|
- name: Remove cloud-init disk for libvirt
|
||||||
community.vmware.vmware_guest:
|
when:
|
||||||
name: "{{ hostname }}"
|
- hypervisor_type == "libvirt"
|
||||||
folder: "{{ system_cfg.path | default('/') }}"
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Remove failed Xen VM
|
|
||||||
when: hypervisor_type == "xen"
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
block:
|
ansible.builtin.file:
|
||||||
- name: Destroy Xen VM if running
|
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
||||||
ansible.builtin.command:
|
state: absent
|
||||||
argv:
|
|
||||||
- xl
|
|
||||||
- destroy
|
|
||||||
- "{{ hostname }}"
|
|
||||||
register: cleanup_xen_destroy
|
|
||||||
failed_when: false
|
|
||||||
changed_when: cleanup_xen_destroy.rc == 0
|
|
||||||
|
|
||||||
- name: Remove Xen VM disks
|
- name: Remove VM for proxmox
|
||||||
ansible.builtin.file:
|
when:
|
||||||
path: "{{ item.path }}"
|
- hypervisor_type == "proxmox"
|
||||||
state: absent
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
loop: "{{ virtualization_xen_disks | default([]) }}"
|
delegate_to: localhost
|
||||||
loop_control:
|
become: false
|
||||||
label: "{{ item.path }}"
|
community.proxmox.proxmox_kvm:
|
||||||
|
api_host: "{{ hypervisor_cfg.url }}"
|
||||||
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
|
api_password: "{{ hypervisor_cfg.password }}"
|
||||||
|
node: "{{ hypervisor_cfg.host }}"
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
vmid: "{{ system_cfg.id }}"
|
||||||
|
state: stopped
|
||||||
|
|
||||||
- name: Remove Xen VM config file
|
- name: Delete VM for proxmox
|
||||||
ansible.builtin.file:
|
when:
|
||||||
path: "/tmp/xen-{{ hostname }}.cfg"
|
- hypervisor_type == "proxmox"
|
||||||
state: absent
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
community.proxmox.proxmox_kvm:
|
||||||
|
api_host: "{{ hypervisor_cfg.url }}"
|
||||||
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
|
api_password: "{{ hypervisor_cfg.password }}"
|
||||||
|
node: "{{ hypervisor_cfg.host }}"
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
vmid: "{{ system_cfg.id }}"
|
||||||
|
state: absent
|
||||||
|
unprivileged: false
|
||||||
|
|
||||||
|
- name: Remove VM for VMware
|
||||||
|
when:
|
||||||
|
- hypervisor_type == "vmware"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
community.vmware.vmware_guest:
|
||||||
|
hostname: "{{ hypervisor_cfg.url }}"
|
||||||
|
username: "{{ hypervisor_cfg.username }}"
|
||||||
|
password: "{{ hypervisor_cfg.password }}"
|
||||||
|
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
folder: "{{ system_cfg.path | default('/') }}"
|
||||||
|
state: poweredoff
|
||||||
|
|
||||||
|
- name: Delete VM for VMware
|
||||||
|
when:
|
||||||
|
- hypervisor_type == "vmware"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
community.vmware.vmware_guest:
|
||||||
|
hostname: "{{ hypervisor_cfg.url }}"
|
||||||
|
username: "{{ hypervisor_cfg.username }}"
|
||||||
|
password: "{{ hypervisor_cfg.password }}"
|
||||||
|
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
name: "{{ hostname }}"
|
||||||
|
folder: "{{ system_cfg.path | default('/') }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Destroy Xen VM if running
|
||||||
|
when:
|
||||||
|
- hypervisor_type == "xen"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
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 disk
|
||||||
|
when:
|
||||||
|
- hypervisor_type == "xen"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: absent
|
||||||
|
loop: "{{ virtualization_xen_disks | default([]) }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
|
||||||
|
- name: Remove Xen VM config file
|
||||||
|
when:
|
||||||
|
- hypervisor_type == "xen"
|
||||||
|
- virtualization_vm_created_in_run | default(false) | bool
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "/tmp/xen-{{ hostname }}.cfg"
|
||||||
|
state: absent
|
||||||
|
|
||||||
- name: VM cleanup completed
|
- name: VM cleanup completed
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
|
|||||||
@@ -3,45 +3,38 @@
|
|||||||
when: hypervisor_type == "vmware"
|
when: hypervisor_type == "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_type == "vmware"
|
||||||
community.vmware.vmware_guest:
|
community.vmware.vmware_guest:
|
||||||
|
hostname: "{{ hypervisor_cfg.url }}"
|
||||||
|
username: "{{ hypervisor_cfg.username }}"
|
||||||
|
password: "{{ hypervisor_cfg.password }}"
|
||||||
|
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
datacenter: "{{ hypervisor_cfg.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_type == "vmware"
|
||||||
vmware.vmware.vm_powerstate:
|
vmware.vmware.vm_powerstate:
|
||||||
|
hostname: "{{ hypervisor_cfg.url }}"
|
||||||
|
username: "{{ hypervisor_cfg.username }}"
|
||||||
|
password: "{{ hypervisor_cfg.password }}"
|
||||||
|
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
datacenter: "{{ hypervisor_cfg.datacenter }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
state: powered-on
|
state: powered-on
|
||||||
|
|||||||
@@ -3,15 +3,36 @@
|
|||||||
when: hypervisor_type == "xen"
|
when: hypervisor_type == "xen"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
vars:
|
|
||||||
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
|
||||||
block:
|
block:
|
||||||
- name: Ensure Xen disk definitions exist
|
- name: Ensure Xen disk definitions exist
|
||||||
ansible.builtin.include_tasks: ../../virtualization/tasks/_xen_disks.yml
|
when: virtualization_xen_disks is not defined
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cleanup_xen_disks: "{{ cleanup_xen_disks | default([]) + [cleanup_xen_disk_cfg] }}"
|
||||||
|
vars:
|
||||||
|
device_letter_map: "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
|
||||||
|
cleanup_xen_disk_cfg: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'path': (
|
||||||
|
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
|
||||||
|
if ansible_loop.index0 == 0
|
||||||
|
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
|
||||||
|
),
|
||||||
|
'target': 'xvd' ~ device_letter,
|
||||||
|
'size': (item.size | float)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
loop: "{{ system_cfg.disks }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item | to_json }}"
|
||||||
|
extended: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Render Xen VM configuration without installer media
|
- name: Render Xen VM configuration without installer media
|
||||||
vars:
|
vars:
|
||||||
xen_installer_media_enabled: false
|
xen_installer_media_enabled: false
|
||||||
|
virtualization_xen_disks: "{{ virtualization_xen_disks | default(cleanup_xen_disks | default([])) }}"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: xen.cfg.j2
|
src: xen.cfg.j2
|
||||||
dest: /tmp/xen-{{ hostname }}.cfg
|
dest: /tmp/xen-{{ hostname }}.cfg
|
||||||
@@ -35,8 +56,3 @@
|
|||||||
- /tmp/xen-{{ hostname }}.cfg
|
- /tmp/xen-{{ hostname }}.cfg
|
||||||
register: cleanup_xen_start_result
|
register: cleanup_xen_start_result
|
||||||
changed_when: cleanup_xen_start_result.rc == 0
|
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 +1,5 @@
|
|||||||
---
|
---
|
||||||
# Network backend is detected per host from the target rootfs in network.yml;
|
configuration_motd_enabled: "{{ system_cfg.features.banner.motd | bool }}"
|
||||||
# no static map needed.
|
configuration_sudo_banner_enabled: "{{ system_cfg.features.banner.sudo | bool }}"
|
||||||
|
configuration_firewall_enabled: "{{ system_cfg.features.firewall.enabled | bool }}"
|
||||||
|
configuration_luks_enabled: "{{ system_cfg.luks.enabled | bool }}"
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- name: Configure MOTD
|
- name: Configure MOTD
|
||||||
when: system_cfg.features.banner.motd | bool
|
when: configuration_motd_enabled | bool
|
||||||
block:
|
block:
|
||||||
- name: Create MOTD file
|
- name: Create MOTD file
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
@@ -23,56 +23,33 @@
|
|||||||
- /mnt/etc/motd.d/insights-client
|
- /mnt/etc/motd.d/insights-client
|
||||||
failed_when: false
|
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
|
- name: Configure sudo banner
|
||||||
when: system_cfg.features.banner.sudo | bool
|
when: configuration_sudo_banner_enabled | bool
|
||||||
block:
|
block:
|
||||||
- name: Detect the target sudo implementation
|
- name: Create sudoers banner directory
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/sudo --version"
|
ansible.builtin.file:
|
||||||
register: configuration_sudo_version
|
path: /mnt/etc/sudoers.d
|
||||||
changed_when: false
|
state: directory
|
||||||
failed_when: false
|
mode: "0755"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
# sudo-rs (Ubuntu 25.10+) implements neither `lecture` nor `lecture_file`
|
- name: Create sudo banner file
|
||||||
# and warns on every sudo call when they are set. It prints its version banner
|
ansible.builtin.copy:
|
||||||
# to stderr, not stdout, so match against both streams.
|
content: |
|
||||||
- name: Configure the sudo lecture
|
I am Groot, and I know what I'm doing.
|
||||||
when: "'sudo-rs' not in (configuration_sudo_version.stdout ~ configuration_sudo_version.stderr)"
|
dest: /mnt/etc/sudoers.d/banner
|
||||||
block:
|
mode: "0644"
|
||||||
- name: Create sudo lecture file
|
owner: root
|
||||||
ansible.builtin.copy:
|
group: root
|
||||||
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
|
- name: Enable sudo banner in sudoers
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/sudoers
|
path: /mnt/etc/sudoers
|
||||||
line: "{{ item }}"
|
line: "Defaults lecture=@/etc/sudoers.d/banner"
|
||||||
state: present
|
state: present
|
||||||
create: true
|
create: true
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
validate: "/usr/sbin/visudo --check --file=%s"
|
validate: "visudo -cf - %s"
|
||||||
loop:
|
|
||||||
- "Defaults lecture=always"
|
|
||||||
- "Defaults lecture_file=/etc/sudo_lecture"
|
|
||||||
|
|||||||
@@ -1,51 +1,29 @@
|
|||||||
---
|
---
|
||||||
- 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 == "rhel" 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: "{{ chroot_command }} {{ 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 system_cfg.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)(.*)"
|
||||||
@@ -53,67 +31,35 @@
|
|||||||
backrefs: true
|
backrefs: true
|
||||||
|
|
||||||
- name: Regenerate initramfs
|
- name: Regenerate initramfs
|
||||||
when: _configuration_platform.initramfs_cmd | length > 0
|
when: os | lower not in ["alpine", "void"]
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ _configuration_platform.initramfs_cmd }}"
|
vars:
|
||||||
|
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: "{{ chroot_command }} {{ 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 == "rhel" 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: "{{ chroot_command }} {{ 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,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
- name: Configure disk encryption
|
- name: Configure disk encryption
|
||||||
when: system_cfg.luks.enabled | bool
|
when: system_cfg.luks.enabled | bool
|
||||||
no_log: true
|
|
||||||
vars:
|
vars:
|
||||||
configuration_luks_passphrase: >-
|
configuration_luks_passphrase: >-
|
||||||
{{ system_cfg.luks.passphrase | string }}
|
{{ system_cfg.luks.passphrase | string }}
|
||||||
block:
|
block:
|
||||||
- name: Set LUKS configuration facts
|
- name: Set LUKS configuration facts
|
||||||
vars:
|
vars:
|
||||||
_raw_pcrs: >-
|
luks_tpm2_pcrs: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
system_cfg.luks.tpm2.pcrs
|
system_cfg.luks.tpm2.pcrs
|
||||||
@@ -20,17 +19,6 @@
|
|||||||
| 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: "{{ system_cfg.luks.mapper }}"
|
||||||
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
||||||
@@ -44,15 +32,10 @@
|
|||||||
'manual'
|
'manual'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
|
configuration_luks_tpm2_device: "{{ partitioning_luks_tpm2_device }}"
|
||||||
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
||||||
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
||||||
configuration_luks_tpm2_token_lib: >-
|
changed_when: false
|
||||||
{{
|
|
||||||
'/usr/lib/x86_64-linux-gnu/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
|
|
||||||
if os_family == 'Debian'
|
|
||||||
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:
|
||||||
@@ -68,27 +51,14 @@
|
|||||||
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
|
fail_msg: system.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' }}"
|
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
|
||||||
@@ -100,7 +70,7 @@
|
|||||||
}}
|
}}
|
||||||
luks_tpm2_option_list: >-
|
luks_tpm2_option_list: >-
|
||||||
{{
|
{{
|
||||||
(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]
|
||||||
@@ -144,16 +114,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:
|
||||||
|
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
|
||||||
|
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: 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:
|
||||||
|
kernel_cmdline_current: >-
|
||||||
|
{{ configuration_kernel_cmdline_slurp.content | b64decode | trim }}
|
||||||
|
kernel_cmdline_list: >-
|
||||||
|
{{
|
||||||
|
kernel_cmdline_current.split()
|
||||||
|
if kernel_cmdline_current | length > 0 else []
|
||||||
|
}}
|
||||||
|
kernel_cmdline_filtered: >-
|
||||||
|
{{
|
||||||
|
kernel_cmdline_list
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
kernel_cmdline_new: >-
|
||||||
|
{{
|
||||||
|
(kernel_cmdline_filtered + configuration_luks_kernel_args.split())
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_kernel_cmdline_new: "{{ kernel_cmdline_new }}"
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
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 }}"
|
|
||||||
@@ -86,6 +86,7 @@
|
|||||||
device: "{{ configuration_luks_device }}"
|
device: "{{ configuration_luks_device }}"
|
||||||
passphrase: "{{ configuration_luks_passphrase }}"
|
passphrase: "{{ configuration_luks_passphrase }}"
|
||||||
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 }}"
|
||||||
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]
|
||||||
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
||||||
+ [configuration_luks_device]
|
+ [configuration_luks_device]
|
||||||
}}
|
}}
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ _enroll_args | join(' ') }}"
|
configuration_luks_enroll_chroot_cmd: >-
|
||||||
register: _tpm2_enroll_result
|
{{ chroot_command }} {{ configuration_luks_enroll_args | join(' ') }}
|
||||||
changed_when: _tpm2_enroll_result.rc == 0
|
ansible.builtin.command: "{{ configuration_luks_enroll_chroot_cmd }}"
|
||||||
|
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]
|
||||||
|
if configuration_luks_tpm2_pcrs | 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,12 +22,13 @@
|
|||||||
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 != "debian" or (os_version | string) != "11") and os != "rhel"
|
- (os != "debian" or (os_version | string) != "11") and os != "rhel"
|
||||||
|
- os | lower not in ["alpine", "void"]
|
||||||
- system_cfg.features.swap.enabled | bool
|
- 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
|
||||||
@@ -42,7 +41,32 @@
|
|||||||
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 == "rhel"
|
||||||
|
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"
|
|
||||||
@@ -23,19 +23,8 @@
|
|||||||
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 == "rhel"
|
||||||
- os == "rhel"
|
|
||||||
- system_cfg.content.source == "dvd"
|
|
||||||
vars:
|
vars:
|
||||||
configuration_fstab_dvd_line: >-
|
configuration_fstab_dvd_line: >-
|
||||||
{{
|
{{
|
||||||
@@ -50,10 +39,7 @@
|
|||||||
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 == "rhel" and hypervisor_type == 'vmware'
|
||||||
- os == "rhel"
|
|
||||||
- hypervisor_type == "vmware"
|
|
||||||
- system_cfg.content.source == "dvd"
|
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
argv:
|
argv:
|
||||||
- dd
|
- dd
|
||||||
@@ -72,9 +58,8 @@
|
|||||||
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,20 +1,18 @@
|
|||||||
---
|
---
|
||||||
- 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:
|
||||||
@@ -22,7 +20,7 @@
|
|||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
partitioning_main_uuid.stdout
|
partitioning_main_uuid.stdout
|
||||||
if system_cfg.filesystem == 'btrfs'
|
if (system_cfg.filesystem | lower) == 'btrfs'
|
||||||
else (partitioning_uuid_root | default([]) | first | default(''))
|
else (partitioning_uuid_root | default([]) | first | default(''))
|
||||||
)
|
)
|
||||||
| default('')
|
| default('')
|
||||||
@@ -38,32 +36,31 @@
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if system_cfg.filesystem != 'btrfs'
|
if (system_cfg.filesystem | lower) != 'btrfs'
|
||||||
else []
|
else []
|
||||||
}}
|
}}
|
||||||
grub_root_flags: >-
|
grub_root_flags: >-
|
||||||
{{ ['rootflags=subvol=@'] if system_cfg.filesystem == 'btrfs' else [] }}
|
{{ ['rootflags=subvol=@'] if (system_cfg.filesystem | lower) == 'btrfs' else [] }}
|
||||||
# String-concat (not list-concat like grub_kernel_cmdline_base below): ansible-lint's
|
|
||||||
# jinja render trips on list+list when grub_lvm_args leads the expression here.
|
|
||||||
grub_cmdline_linux_base: >-
|
grub_cmdline_linux_base: >-
|
||||||
{{
|
{{
|
||||||
((grub_lvm_args | join(' ')) ~ ' ' ~ (_hardware_profile_kernel_params | default([]) | join(' '))) | trim
|
(['crashkernel=auto'] + grub_lvm_args)
|
||||||
|
| join(' ')
|
||||||
}}
|
}}
|
||||||
grub_kernel_cmdline_base: >-
|
grub_kernel_cmdline_base: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
(['root=UUID=' + grub_root_uuid]
|
(['root=UUID=' + grub_root_uuid]
|
||||||
if grub_root_uuid | length > 0 else [])
|
if grub_root_uuid | length > 0 else [])
|
||||||
+ ['ro']
|
+ ['ro', 'crashkernel=auto']
|
||||||
+ grub_lvm_args
|
+ grub_lvm_args
|
||||||
+ grub_root_flags
|
+ grub_root_flags
|
||||||
+ (_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: "{{ grub_cmdline_linux_base }}"
|
||||||
configuration_kernel_cmdline_base: "{{ grub_kernel_cmdline_base }}"
|
configuration_kernel_cmdline_base: "{{ grub_kernel_cmdline_base }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Check if grub defaults file exists
|
- name: Check if grub defaults file exists
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
@@ -98,10 +95,22 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
- 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,52 +14,59 @@
|
|||||||
- 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: "{{ chroot_command }} /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
|
|
||||||
ansible.builtin.set_fact:
|
- name: Set hostname
|
||||||
configuration_dns_domain: >-
|
vars:
|
||||||
{{ (system_cfg.network.dns.search | default([]) | first | default('')) | string }}
|
configuration_dns_domain: "{{ (system_cfg.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 + '.' + configuration_dns_domain
|
||||||
if (system_cfg.network.dns.search | default([]) | first | default('') | string) | length > 0
|
if configuration_dns_domain | length > 0
|
||||||
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_dns_domain: "{{ (system_cfg.dns.search | default([]) | first | default('')) | string }}"
|
||||||
|
configuration_hostname_fqdn: >-
|
||||||
|
{{
|
||||||
|
hostname
|
||||||
|
if '.' in hostname
|
||||||
|
else (
|
||||||
|
hostname + '.' + configuration_dns_domain
|
||||||
|
if configuration_dns_domain | length > 0
|
||||||
|
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: >-
|
configuration_hosts_ip: >-
|
||||||
{{
|
{{
|
||||||
system_cfg.network.ip
|
system_cfg.ip
|
||||||
if system_cfg.network.ip is defined and (system_cfg.network.ip | string | length) > 0
|
if system_cfg.ip is defined and (system_cfg.ip | string | length) > 0
|
||||||
else inventory_hostname
|
else inventory_hostname
|
||||||
}}
|
}}
|
||||||
configuration_hosts_line: >-
|
configuration_hosts_line: >-
|
||||||
@@ -71,12 +78,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,18 @@
|
|||||||
---
|
---
|
||||||
- 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
|
- banner.yml
|
||||||
- file: banner.yml
|
- fstab.yml
|
||||||
- file: fstab.yml
|
- locales.yml
|
||||||
- file: locales.yml
|
- services.yml
|
||||||
- file: ssh.yml
|
- grub.yml
|
||||||
- file: services.yml
|
- encryption.yml
|
||||||
- file: firewall.yml
|
- bootloader.yml
|
||||||
- file: grub.yml
|
- extras.yml
|
||||||
- file: encryption.yml
|
- network.yml
|
||||||
when: "{{ system_cfg.luks.enabled | bool }}"
|
- users.yml
|
||||||
- file: bootloader.yml
|
- sudo.yml
|
||||||
- file: secure_boot.yml
|
- selinux.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,165 @@
|
|||||||
---
|
---
|
||||||
- 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_detected: >-
|
||||||
|
{{ configuration_net_inf_from_facts | default(configuration_net_inf_from_ip, true) }}
|
||||||
|
configuration_net_inf_regex: "{{ configuration_net_inf_detected | 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_detected, {}).get('macaddress', '')
|
||||||
|
)
|
||||||
|
| default(
|
||||||
|
(ansible_facts | default({})).get('ansible_' + configuration_net_inf_detected, {}).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_detected }}"
|
||||||
|
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: Configure NetworkManager profile
|
||||||
|
when: os | lower not in ["alpine", "void"]
|
||||||
|
block:
|
||||||
|
- name: Copy NetworkManager keyfile
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: network.j2
|
||||||
|
dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
|
- name: Fix Ubuntu unmanaged devices
|
||||||
|
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"
|
||||||
|
|
||||||
|
- name: Configure Alpine networking
|
||||||
|
when: os | lower == "alpine"
|
||||||
vars:
|
vars:
|
||||||
_found: "{{ configuration_net_probe.results | selectattr('stat.exists') | map(attribute='item') | list }}"
|
configuration_dns_list: "{{ system_cfg.dns.servers | default([]) }}"
|
||||||
ansible.builtin.set_fact:
|
configuration_alpine_static: >-
|
||||||
configuration_network_backend: >-
|
|
||||||
{{
|
{{
|
||||||
'nm' if (['/mnt/usr/bin/nmcli', '/mnt/usr/lib/systemd/system/NetworkManager.service'] | intersect(_found))
|
system_cfg.ip is defined
|
||||||
else 'netplan' if (['/mnt/usr/sbin/netplan', '/mnt/etc/netplan'] | intersect(_found))
|
and system_cfg.ip | string | length > 0
|
||||||
else 'eni' if (['/mnt/sbin/ifup', '/mnt/usr/sbin/ifup'] | intersect(_found))
|
and system_cfg.prefix is defined
|
||||||
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))
|
and (system_cfg.prefix | string | length) > 0
|
||||||
else 'nm'
|
|
||||||
}}
|
}}
|
||||||
|
block:
|
||||||
|
- name: Write Alpine network interfaces
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/network/interfaces
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
- name: Configure networking for the detected backend {{ configuration_network_backend }}
|
auto {{ configuration_net_inf }}
|
||||||
ansible.builtin.include_tasks: "network_{{ configuration_network_backend }}.yml"
|
iface {{ configuration_net_inf }} inet {{ 'static' if configuration_alpine_static | bool else 'dhcp' }}
|
||||||
|
{% if configuration_alpine_static | bool %}
|
||||||
|
address {{ system_cfg.ip }}/{{ system_cfg.prefix }}
|
||||||
|
{% if system_cfg.gateway is defined and system_cfg.gateway | string | length %}
|
||||||
|
gateway {{ system_cfg.gateway }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- name: Set Alpine DNS resolvers
|
||||||
|
when: configuration_dns_list | length > 0
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/resolv.conf
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
{% for resolver in configuration_dns_list %}
|
||||||
|
nameserver {{ resolver }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
- name: Configure Void networking
|
||||||
|
when: os | lower == "void"
|
||||||
|
vars:
|
||||||
|
configuration_dns_list: "{{ system_cfg.dns.servers | default([]) }}"
|
||||||
|
configuration_void_static: >-
|
||||||
|
{{
|
||||||
|
system_cfg.ip is defined
|
||||||
|
and system_cfg.ip | string | length > 0
|
||||||
|
and system_cfg.prefix is defined
|
||||||
|
and (system_cfg.prefix | string | length) > 0
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Write dhcpcd configuration for static networking
|
||||||
|
when: configuration_void_static | bool
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/dhcpcd.conf
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
interface {{ configuration_net_inf }}
|
||||||
|
static ip_address={{ system_cfg.ip }}/{{ system_cfg.prefix }}
|
||||||
|
{% if system_cfg.gateway is defined and system_cfg.gateway | string | length %}
|
||||||
|
static routers={{ system_cfg.gateway }}
|
||||||
|
{% endif %}
|
||||||
|
{% if configuration_dns_list | length > 0 %}
|
||||||
|
static domain_name_servers={{ configuration_dns_list | join(' ') }}
|
||||||
|
{% endif %}
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- 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 in ['almalinux', 'rocky', 'rhel'] and system_cfg.features.selinux.enabled | bool
|
||||||
@@ -11,20 +11,8 @@
|
|||||||
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 system_cfg.features.selinux.enabled | bool
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/etc/selinux/config
|
path: /mnt/etc/selinux/config
|
||||||
regexp: ^SELINUX=
|
regexp: ^SELINUX=
|
||||||
|
|||||||
@@ -1,248 +1,79 @@
|
|||||||
---
|
---
|
||||||
- name: Resolve desktop facts
|
- name: Enable Systemd Services
|
||||||
when: system_cfg.features.desktop.enabled | bool
|
when: os | lower not in ['alpine', 'void']
|
||||||
|
ansible.builtin.command: >
|
||||||
|
{{ chroot_command }} systemctl enable NetworkManager
|
||||||
|
{{ ' 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 '' }}
|
||||||
|
{{
|
||||||
|
(' ssh' if is_debian | bool else ' sshd')
|
||||||
|
if system_cfg.features.ssh.enabled | bool else ''
|
||||||
|
}}
|
||||||
|
{{
|
||||||
|
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
|
||||||
|
if os | lower == 'archlinux' else ''
|
||||||
|
}}
|
||||||
|
register: configuration_enable_services_result
|
||||||
|
changed_when: configuration_enable_services_result.rc == 0
|
||||||
|
|
||||||
|
- name: Enable OpenRC services
|
||||||
|
when: os | lower == 'alpine'
|
||||||
vars:
|
vars:
|
||||||
_autologin: "{{ system_cfg.features.desktop.autologin | default(false) }}"
|
configuration_openrc_services: >-
|
||||||
ansible.builtin.set_fact:
|
|
||||||
# KDE resolves to the plasmalogin unit on Arch/Fedora44+ (Plasma 6.6), else sddm.
|
|
||||||
_desktop_dm: >-
|
|
||||||
{{
|
{{
|
||||||
('plasmalogin'
|
['networking']
|
||||||
if system_cfg.features.desktop.display_manager == 'plasma-login-manager'
|
+ (['sshd'] if system_cfg.features.ssh.enabled | bool else [])
|
||||||
else system_cfg.features.desktop.display_manager)
|
+ ([system_cfg.features.firewall.backend] if system_cfg.features.firewall.enabled | bool else [])
|
||||||
if (system_cfg.features.desktop.display_manager | length > 0)
|
|
||||||
else (
|
|
||||||
('plasmalogin'
|
|
||||||
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
|
|
||||||
when: _configuration_platform.init_system == 'systemd'
|
|
||||||
vars:
|
|
||||||
configuration_systemd_services: >-
|
|
||||||
{{
|
|
||||||
['NetworkManager', _configuration_platform.time_sync_service]
|
|
||||||
+ ([_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:
|
block:
|
||||||
- name: Enable ly display manager
|
- name: Ensure OpenRC runlevel directory exists
|
||||||
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:
|
ansible.builtin.file:
|
||||||
path: /mnt/etc/greetd
|
path: /mnt/etc/runlevels/default
|
||||||
state: directory
|
state: directory
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: Write greetd config.toml
|
- name: Check OpenRC init scripts
|
||||||
ansible.builtin.template:
|
ansible.builtin.stat:
|
||||||
src: greetd-config.toml.j2
|
path: "/mnt/etc/init.d/{{ item }}"
|
||||||
dest: /mnt/etc/greetd/config.toml
|
loop: "{{ configuration_openrc_services }}"
|
||||||
mode: "0644"
|
register: configuration_openrc_service_stats
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Configure GDM autologin
|
- name: Enable OpenRC services
|
||||||
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:
|
ansible.builtin.file:
|
||||||
path: "{{ _gdm_dir }}"
|
src: "/mnt/etc/init.d/{{ item.item }}"
|
||||||
|
dest: "/mnt/etc/runlevels/default/{{ item.item }}"
|
||||||
|
state: link
|
||||||
|
loop: "{{ configuration_openrc_service_stats.results }}"
|
||||||
|
when: item.stat.exists
|
||||||
|
|
||||||
|
- name: Enable runit services
|
||||||
|
when: os | lower == 'void'
|
||||||
|
vars:
|
||||||
|
configuration_runit_services: >-
|
||||||
|
{{
|
||||||
|
['dhcpcd']
|
||||||
|
+ (['sshd'] if system_cfg.features.ssh.enabled | bool else [])
|
||||||
|
+ ([system_cfg.features.firewall.backend] if system_cfg.features.firewall.enabled | bool else [])
|
||||||
|
}}
|
||||||
|
block:
|
||||||
|
- name: Ensure runit service directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /mnt/var/service
|
||||||
state: directory
|
state: directory
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
- name: Write GDM autologin config
|
- name: Check runit service definitions
|
||||||
ansible.builtin.template:
|
ansible.builtin.stat:
|
||||||
src: gdm-custom.conf.j2
|
path: "/mnt/etc/sv/{{ item }}"
|
||||||
dest: "{{ _gdm_dir }}/{{ _gdm_conf }}"
|
loop: "{{ configuration_runit_services }}"
|
||||||
mode: "0644"
|
register: configuration_runit_service_stats
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
# SDDM and plasma-login-manager share the [Autologin] format and the KDE Wayland
|
- name: Enable runit services
|
||||||
# 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:
|
ansible.builtin.file:
|
||||||
path: "{{ _autologin_conf_dir }}"
|
src: "/mnt/etc/sv/{{ item.item }}"
|
||||||
state: directory
|
dest: "/mnt/var/service/{{ item.item }}"
|
||||||
mode: "0755"
|
state: link
|
||||||
|
loop: "{{ configuration_runit_service_stats.results }}"
|
||||||
# Plasma 6 ships the Wayland session as plasma.desktop; Plasma 5 ships it as
|
when: item.stat.exists
|
||||||
# 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
|
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||||
--uid {{ 1000 + _idx }}
|
--groups {{ configuration_user_group }} {{ system_cfg.user.name }}
|
||||||
--groups {{ configuration_user_group }} {{ item.key }}
|
--password {{ system_cfg.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') }}
|
{{ chroot_command }} /usr/sbin/usermod --password
|
||||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
'{{ system_cfg.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: system_cfg.user.key | length > 0
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "/mnt/home/{{ item.key }}/.ssh"
|
path: /mnt/home/{{ system_cfg.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: system_cfg.user.key | length > 0
|
||||||
ansible.builtin.copy:
|
ansible.builtin.lineinfile:
|
||||||
content: "{{ item.value['keys'] | join('\n') }}\n"
|
path: /mnt/home/{{ system_cfg.user.name }}/.ssh/authorized_keys
|
||||||
dest: "/mnt/home/{{ item.key }}/.ssh/authorized_keys"
|
line: "{{ system_cfg.user.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,25 @@
|
|||||||
[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 %}
|
|
||||||
{# Bind the first available ethernet by name glob, never a MAC: a clone with a new adapter/MAC stays networked (#12). #}
|
|
||||||
|
|
||||||
[match]
|
|
||||||
interface-name=en*;eth*;
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
[ipv4]
|
[ipv4]
|
||||||
{% set dns_list = configuration_dns_list %}
|
{% set dns_list = system_cfg.dns.servers | default([]) %}
|
||||||
{% set search_list = configuration_dns_search %}
|
{% set search_list = system_cfg.dns.search | default([]) %}
|
||||||
{% if iface.ip | default('') | string | length %}
|
{% if system_cfg.ip is defined and system_cfg.ip | string | length %}
|
||||||
address1={{ iface.ip }}/{{ iface.prefix }}{{ (',' ~ iface.gateway) if (iface.gateway | default('') | string | length) else '' }}
|
address1={{ system_cfg.ip }}/{{ system_cfg.prefix }}{{ (',' ~ system_cfg.gateway) if (system_cfg.gateway is defined and system_cfg.gateway | string | 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 %}
|
||||||
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 %}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user