Compare commits
84 Commits
master
...
c9a15dfccf
| Author | SHA1 | Date | |
|---|---|---|---|
| c9a15dfccf | |||
| f83a9ebd67 | |||
| e16868a78d | |||
| 406db38296 | |||
| cb3f36a040 | |||
| d97f0cfff8 | |||
| e8f609dd03 | |||
| a599e26a63 | |||
| 3085ebc336 | |||
| f967ea1c3b | |||
| 2c4995ede8 | |||
| ccf3193c92 | |||
| d92944c345 | |||
| 3c94a33ae7 | |||
| af82baf1d8 | |||
| ec55701f00 | |||
| 2a1a47ecc1 | |||
| 4808ce4401 | |||
| db1fd13623 | |||
| e5660b0ba7 | |||
| 173ecd299b | |||
| 4d242ad987 | |||
| f8ac22cfab | |||
| 12a7549aaa | |||
| 6705411b2d | |||
| fe2b216fc7 | |||
| 26824ca6bb | |||
| c60fcca86d | |||
| cdd8062937 | |||
| ebedff1c4e | |||
| 04d05a4e8b | |||
| ee6e06a3fe | |||
| 527bc11d1d | |||
| d331e07536 | |||
| 287036bcb4 | |||
| ca5a3c8807 | |||
| c8dd89681b | |||
| 9d4af56976 | |||
| 3c55eaf4a1 | |||
| d905dce89e | |||
| 76f1382e3e | |||
| 04c27cd7d0 | |||
| 147430b36e | |||
| f8ba5c41db | |||
| 7a4fc24f32 | |||
| 7bf7c29291 | |||
| ccfce65673 | |||
| 528f2fc775 | |||
| 505110f580 | |||
| 1d1b2fff42 | |||
| 4cf4816be0 | |||
| e37b5a535b | |||
| 5312ec8cc6 | |||
| a3b772c543 | |||
| adde811f47 | |||
| f788767839 | |||
| 8b773d2304 | |||
| c988ab8f9a | |||
| 8864db253b | |||
| 06ca8d8787 | |||
| 374b5fc7ef | |||
| 6bfd530c90 | |||
| b077e549db | |||
| 43ce280d11 | |||
| a6b51b4cb4 | |||
| 6dd31cc95f | |||
| 4b98ec1434 | |||
| 2444c5d7af | |||
| ec6ca49265 | |||
| fe43bf6733 | |||
| 31c155ce92 | |||
| 0c75114b94 | |||
| cd9ed65c91 | |||
| 9986d19ed6 | |||
| d73e78c5f2 | |||
| b6f620fb70 | |||
| cc40bae858 | |||
| 344753fa5b | |||
| 6be464a0e2 | |||
| 48b5f602fa | |||
| cc118274a3 | |||
|
|
d733513e29 | ||
|
|
402f2b9bc0 | ||
| 4ec5432989 |
@@ -1,6 +0,0 @@
|
||||
skip_list:
|
||||
- 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:
|
||||
- roles/global_defaults/
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,6 +6,3 @@ vars.yml
|
||||
vars.yaml
|
||||
vars_kvm.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
|
||||
551
README.md
551
README.md
@@ -1,458 +1,111 @@
|
||||
# Ansible Bootstrap
|
||||
# Ansible-Bootstrap
|
||||
|
||||
Automated Linux system bootstrap using the Arch Linux ISO as a universal installer. Deploys any supported distribution on virtual or physical targets via Infrastructure-as-Code.
|
||||
An Ansible playbook for automating system bootstrap processes in an Infrastructure-as-Code manner, utilizing ArchISO as the foundational tool.
|
||||
|
||||
Non-Arch targets require the appropriate package manager available from the ISO environment (e.g. `dnf` for RHEL-family). Set `system.features.chroot.tool` if `arch-chroot` is unavailable.
|
||||
# Info
|
||||
Most of the roles are adaptable for use with systems beyond ArchLinux, requiring only that the target system can install a necessary package manager, such as `dnf` for RHEL-based systems. Additionally, a replacement for the `arch-chroot` command may be required for these systems.
|
||||
|
||||
**NOTE**:
|
||||
- For RHEL 8 and RHEL 9, repository access requires the `rhel_iso` variable. This variable specifies a local ISO or proxy repository.
|
||||
- RHEL systems do not support `btrfs`. Use `ext4` or `xfs` as alternatives.
|
||||
- For RHEL 8, `xfs` may cause installation issues; `ext4` is recommended.
|
||||
|
||||
# Supported Distributions
|
||||
|
||||
This playbook supports multiple Linux distributions with specific versions tailored to each. Below is a list of supported distributions:
|
||||
|
||||
| `os` | Distribution |
|
||||
|------------|------------------------------------|
|
||||
| archlinux | ArchLinux (Latest rolling release) |
|
||||
| almalinux | AlmaLinux 9.x |
|
||||
| debian11 | Debian 11 (Bullseye) |
|
||||
| debian12 | Debian 12 (Bookworm) |
|
||||
| fedora | Fedora 41 |
|
||||
| rhel8 | Red Hat Enterprise Linux 8 |
|
||||
| rhel9 | Red Hat Enterprise Linux 9 |
|
||||
| rocky | Rocky Linux 9.x |
|
||||
| ubuntu | Ubuntu 24.10 (Oracular Oriole) |
|
||||
| ubuntu-lts | Ubuntu 24.04 LTS (Noble Numbat) |
|
||||
|
||||
# Documentation
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Supported Platforms](#1-supported-platforms)
|
||||
2. [Compatibility Notes](#2-compatibility-notes)
|
||||
3. [Configuration Model](#3-configuration-model)
|
||||
4. [Variable Reference](#4-variable-reference)
|
||||
- 4.1 [Core Variables](#41-core-variables)
|
||||
- 4.2 [`system` Dictionary](#42-system-dictionary)
|
||||
- 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary)
|
||||
- 4.4 [`cis` Dictionary](#44-cis-dictionary)
|
||||
- 4.5 [VMware Guest Operations](#45-vmware-guest-operations)
|
||||
- 4.6 [Multi-Disk Schema](#46-multi-disk-schema)
|
||||
- 4.7 [Advanced Partitioning Overrides](#47-advanced-partitioning-overrides)
|
||||
- 4.8 [Cleanup Defaults](#48-cleanup-defaults)
|
||||
5. [Execution Pipeline](#5-execution-pipeline)
|
||||
6. [Usage](#6-usage)
|
||||
7. [Security](#7-security)
|
||||
8. [Safety](#8-safety)
|
||||
|
||||
## 1. Supported Platforms
|
||||
|
||||
### Distributions
|
||||
|
||||
| `system.os` | Distribution | `system.version` |
|
||||
| ------------ | ------------------------ | ------------------------------------- |
|
||||
| `almalinux` | AlmaLinux | `8`, `9`, `10` |
|
||||
| `alpine` | Alpine Linux | latest (rolling) |
|
||||
| `archlinux` | Arch Linux | latest (rolling) |
|
||||
| `debian` | Debian | `10`-`13`, `unstable` |
|
||||
| `fedora` | Fedora | `38`-`45` |
|
||||
| `opensuse` | openSUSE Tumbleweed | latest (rolling) |
|
||||
| `rhel` | Red Hat Enterprise Linux | `8`, `9`, `10` |
|
||||
| `rocky` | Rocky Linux | `8`, `9`, `10` |
|
||||
| `ubuntu` | Ubuntu (latest non-LTS) | optional (e.g. `24.04`) |
|
||||
| `ubuntu-lts` | Ubuntu LTS | optional (e.g. `24.04`) |
|
||||
| `void` | Void Linux | latest (rolling) |
|
||||
|
||||
### Hypervisors
|
||||
|
||||
| Hypervisor | `hypervisor.type` |
|
||||
| ----------- | ----------------- |
|
||||
| libvirt | `libvirt` |
|
||||
| Proxmox VE | `proxmox` |
|
||||
| VMware | `vmware` |
|
||||
| Xen | `xen` |
|
||||
| Bare metal | `none` |
|
||||
|
||||
## 2. Compatibility Notes
|
||||
|
||||
- `rhel_iso` is required for `system.os: rhel`.
|
||||
- RHEL installs should use `ext4` or `xfs` (not `btrfs`).
|
||||
- `custom_iso: true` skips ArchISO validation; your installer must provide required tooling.
|
||||
- On non-Arch installers, set `system.features.chroot.tool` explicitly.
|
||||
|
||||
## 3. Configuration Model
|
||||
|
||||
Two dict-based variables drive the entire configuration:
|
||||
|
||||
- **`system`** -- host, network, users, disk layout, encryption, and feature toggles
|
||||
- **`hypervisor`** -- virtualization backend credentials and targeting
|
||||
|
||||
An optional third dict **`cis`** overrides CIS hardening parameters when `system.features.cis.enabled: true`.
|
||||
|
||||
All three 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.
|
||||
|
||||
### Variable Placement
|
||||
|
||||
| Location | Scope | Typical use |
|
||||
| ------------------------ | ----------- | -------------------------------------------------------------- |
|
||||
| `group_vars/all.yml` | All hosts | Shared `hypervisor`, `system.filesystem`, `boot_iso` |
|
||||
| `group_vars/<group>.yml` | Group | Environment-specific defaults |
|
||||
| `host_vars/<host>.yml` | Single host | Host-specific overrides (`system.network.ip`, `system.id`, etc.) |
|
||||
|
||||
### Example Inventory
|
||||
|
||||
```yaml
|
||||
all:
|
||||
vars:
|
||||
system:
|
||||
filesystem: btrfs
|
||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
||||
hypervisor:
|
||||
type: proxmox
|
||||
url: pve01.example.com
|
||||
username: root@pam
|
||||
password: !vault |
|
||||
$ANSIBLE_VAULT...
|
||||
node: pve01
|
||||
storage: local-lvm
|
||||
|
||||
children:
|
||||
bootstrap:
|
||||
hosts:
|
||||
app01.example.com:
|
||||
ansible_host: 10.0.0.10
|
||||
system:
|
||||
type: virtual
|
||||
os: debian
|
||||
version: "12"
|
||||
name: app01.example.com
|
||||
id: 101
|
||||
cpus: 2
|
||||
memory: 4096
|
||||
network:
|
||||
bridge: vmbr0
|
||||
ip: 10.0.0.10
|
||||
prefix: 24
|
||||
gateway: 10.0.0.1
|
||||
dns:
|
||||
servers: [1.1.1.1, 1.0.0.1]
|
||||
search: [example.com]
|
||||
disks:
|
||||
- size: 40
|
||||
- size: 120
|
||||
mount:
|
||||
path: /data
|
||||
fstype: xfs
|
||||
users:
|
||||
- name: ops
|
||||
password: !vault |
|
||||
$ANSIBLE_VAULT...
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
sudo: true
|
||||
root:
|
||||
password: !vault |
|
||||
$ANSIBLE_VAULT...
|
||||
luks:
|
||||
enabled: true
|
||||
passphrase: !vault |
|
||||
$ANSIBLE_VAULT...
|
||||
method: tpm2
|
||||
tpm2:
|
||||
pcrs: "7"
|
||||
features:
|
||||
cis:
|
||||
enabled: true
|
||||
firewall:
|
||||
enabled: true
|
||||
backend: firewalld
|
||||
toolkit: nftables
|
||||
```
|
||||
|
||||
## 4. Variable Reference
|
||||
|
||||
### 4.1 Core Variables
|
||||
|
||||
Top-level variables outside `system`/`hypervisor`/`cis`.
|
||||
|
||||
| Variable | Type | Default | Description |
|
||||
| ---------------- | ------ | -------------------------- | ---------------------------------------------------- |
|
||||
| `boot_iso` | string | -- | Boot ISO path (required for virtual installs) |
|
||||
| `rhel_iso` | string | -- | RHEL ISO path (required when `system.os: rhel`) |
|
||||
| `custom_iso` | bool | `false` | Skip ArchISO validation and pacman setup |
|
||||
| `thirdparty_tasks` | string | `dropins/preparation.yml` | Drop-in task file included during environment setup |
|
||||
|
||||
### 4.2 `system` Dictionary
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | ---------- | ------------------ | ------------------------------------------------------ |
|
||||
| `type` | string | `virtual` | `virtual` or `physical` |
|
||||
| `os` | string | -- | Target distribution (see [table](#distributions)) |
|
||||
| `version` | string | -- | Version selector for versioned distros |
|
||||
| `filesystem` | string | -- | `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 |
|
||||
| `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` | list | `[]` | User accounts |
|
||||
| `root` | dict | see below | Root account settings |
|
||||
| `luks` | dict | see below | Encryption settings |
|
||||
| `features` | dict | see below | Feature toggles |
|
||||
|
||||
#### `system.network`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------------- | ---------- | ------- | ---------------------------------------------- |
|
||||
| `bridge` | string | -- | Hypervisor network/bridge name |
|
||||
| `vlan` | string/int | -- | VLAN tag |
|
||||
| `ip` | string | -- | Static IP (omit for DHCP) |
|
||||
| `prefix` | int | -- | CIDR prefix (1-32, required with `ip`) |
|
||||
| `gateway` | string | -- | Default gateway |
|
||||
| `dns.servers` | list | `[]` | DNS resolvers (must be a YAML list) |
|
||||
| `dns.search` | list | `[]` | Search domains (must be a YAML list) |
|
||||
| `interfaces` | list | `[]` | Multi-NIC config (overrides flat fields above) |
|
||||
|
||||
When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway`, `vlan`) are auto-wrapped into a single-entry list. When `interfaces` is set, it takes precedence. Each entry supports: `name`, `bridge` (required), `vlan`, `ip`, `prefix`, `gateway`.
|
||||
|
||||
#### `system.users`
|
||||
|
||||
Dict keyed by username. At least one user must have a `password` (used for SSH access during bootstrap). Users without a password get locked accounts (key-only auth).
|
||||
|
||||
```yaml
|
||||
system:
|
||||
users:
|
||||
svcansible:
|
||||
password: "vault_lookup"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
appuser:
|
||||
sudo: "ALL=(ALL) NOPASSWD: ALL"
|
||||
keys:
|
||||
- "ssh-ed25519 BBBB..."
|
||||
```
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ---------- | ----------- | ------- | -------------------------------------------------- |
|
||||
| *(dict key)* | string | -- | Username (required) |
|
||||
| `password` | string | -- | User password (required for at least one user) |
|
||||
| `keys` | list | `[]` | SSH public keys |
|
||||
| `sudo` | bool/string | -- | `true` for NOPASSWD ALL, or custom sudoers string |
|
||||
|
||||
Users must be defined in inventory. The dict format enables additive merging across inventory layers with `hash_behaviour=merge`.
|
||||
|
||||
#### `system.root`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ---------- | ------ | ------- | ------------- |
|
||||
| `password` | string | -- | Root password |
|
||||
|
||||
#### `system.luks`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | ------ | ------------------ | ------------------------------------------ |
|
||||
| `enabled` | bool | `false` | Enable encrypted root |
|
||||
| `passphrase` | string | -- | Passphrase for format/open/enroll |
|
||||
| `mapper` | string | `SYSTEM_DECRYPTED` | Mapper name under `/dev/mapper` |
|
||||
| `auto` | bool | `true` | Auto-unlock toggle |
|
||||
| `method` | string | `tpm2` | Auto-unlock backend: `tpm2` or `keyfile` |
|
||||
| `keysize` | int | `64` | Keyfile size in bytes |
|
||||
| `options` | string | `discard,tries=3` | Additional crypttab options |
|
||||
| `type` | string | `luks2` | LUKS format type |
|
||||
| `cipher` | string | `aes-xts-plain64` | Cipher |
|
||||
| `hash` | string | `sha512` | Hash algorithm |
|
||||
| `iter` | int | `4000` | PBKDF iteration time (ms) |
|
||||
| `bits` | int | `512` | Key size (bits) |
|
||||
| `pbkdf` | string | `argon2id` | PBKDF algorithm |
|
||||
| `urandom` | bool | `true` | Use urandom during key generation |
|
||||
| `verify` | bool | `true` | Verify passphrase during format |
|
||||
|
||||
#### `system.luks.tpm2`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------- | ------------- | ------- | ---------------------------------------------- |
|
||||
| `device` | string | `auto` | TPM2 device selector |
|
||||
| `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`); empty = no PCR binding |
|
||||
|
||||
**TPM2 auto-unlock:** Uses `systemd-cryptenroll` on all distros. The user-set passphrase
|
||||
remains as a backup unlock method. TPM2 enrollment runs in the chroot during bootstrap;
|
||||
if it fails (e.g. no TPM2 hardware), the system boots with passphrase-only unlock and
|
||||
TPM2 can be enrolled post-deployment via `systemd-cryptenroll --tpm2-device=auto <device>`.
|
||||
|
||||
On Debian/Ubuntu, TPM2 auto-unlock requires dracut (initramfs-tools does not support `tpm2-device`).
|
||||
The bootstrap auto-switches to dracut when `method: tpm2` is set. Override via `features.initramfs.generator`.
|
||||
|
||||
#### `system.features`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------------ | ------ | -------------- | ------------------------------------ |
|
||||
| `cis.enabled` | bool | `false` | Enable CIS hardening (see [4.4](#44-cis-dictionary)) |
|
||||
| `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) |
|
||||
| `desktop.*` | dict | see below | Desktop environment settings (see [4.2.5](#425-systemfeaturesdesktop)) |
|
||||
|
||||
**Initramfs generator auto-detection:** RedHat → dracut, Arch → mkinitcpio, Debian/Ubuntu → initramfs-tools.
|
||||
Override with `dracut`, `mkinitcpio`, or `initramfs-tools`. When LUKS TPM2 auto-unlock is enabled and the
|
||||
native generator does not support `tpm2-device`, the generator is automatically upgraded to dracut.
|
||||
On distros with older dracut (no `tpm2-tss` module), clevis is used as a fallback for TPM2 binding.
|
||||
|
||||
#### 4.2.5 `system.features.desktop`
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ----------------- | ------ | -------------- | ----------------------------------------- |
|
||||
| `enabled` | bool | `false` | Install desktop environment |
|
||||
| `environment` | string | -- | `gnome`, `kde`, `xfce`, `sway`, `hyprland`, `cinnamon`, `mate`, `lxqt`, `budgie` |
|
||||
| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, `lightdm`, `ly`, `greetd` |
|
||||
|
||||
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→gdm, kde→sddm, xfce→lightdm, sway→greetd, hyprland→ly.
|
||||
|
||||
### 4.3 `hypervisor` Dictionary
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------ | ------ | ------- | ---------------------------------------------------- |
|
||||
| `type` | string | -- | `libvirt`, `proxmox`, `vmware`, `xen`, or `none` |
|
||||
| `url` | string | -- | API host (Proxmox/VMware) |
|
||||
| `username` | string | -- | API username |
|
||||
| `password` | string | -- | API password |
|
||||
| `node` | string | -- | Target compute node (Proxmox node / VMware ESXi host; mutually exclusive with `cluster` on VMware) |
|
||||
| `storage` | string | -- | Storage identifier (Proxmox/VMware) |
|
||||
| `datacenter` | string | -- | VMware datacenter |
|
||||
| `cluster` | string | -- | VMware cluster |
|
||||
| `certs` | bool | `true` | TLS certificate validation (VMware) |
|
||||
| `ssh` | bool | `false` | Enable SSH on guest and switch connection (VMware) |
|
||||
|
||||
### 4.4 `cis` Dictionary
|
||||
|
||||
When `system.features.cis.enabled: true`, the CIS role applies hardening. All values have sensible defaults; override specific keys via the `cis` dict.
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------------------- | ------ | ------- | ------------------------------------------------ |
|
||||
| `modules_blacklist` | list | see below | Kernel modules to blacklist via modprobe |
|
||||
| `sysctl` | dict | see below | Sysctl key/value pairs written to `10-cis.conf` |
|
||||
| `sshd_options` | list | see below | SSHD options applied via lineinfile |
|
||||
| `pwquality_minlen` | int | `14` | Minimum password length |
|
||||
| `tmout` | int | `900` | Shell timeout (seconds) |
|
||||
| `umask` | string | `077` | Default umask in bashrc |
|
||||
| `umask_profile` | string | `027` | Default umask in /etc/profile |
|
||||
| `faillock_deny` | int | `5` | Failed login attempts before lockout |
|
||||
| `faillock_unlock_time` | int | `900` | Lockout duration (seconds) |
|
||||
| `password_remember` | int | `5` | Password history depth |
|
||||
|
||||
**Default modules blacklist:** `freevxfs`, `jffs2`, `hfs`, `hfsplus`, `cramfs`, `udf`, `usb-storage`, `dccp`, `sctp`, `rds`, `tipc`, `firewire-core`, `firewire-sbp2`, `thunderbolt`. `squashfs` is added automatically except on Ubuntu (snap dependency).
|
||||
|
||||
**Default sysctl settings** include: `kernel.yama.ptrace_scope=2`, `kernel.kptr_restrict=2`, `kernel.perf_event_paranoid=3`, `kernel.unprivileged_bpf_disabled=1`, IPv4/IPv6 hardening, ARP protection, and IPv6 disabled by default. Override individual keys:
|
||||
|
||||
```yaml
|
||||
cis:
|
||||
sysctl:
|
||||
net.ipv6.conf.all.disable_ipv6: 0 # re-enable IPv6
|
||||
net.ipv4.ip_forward: 1 # enable for routers/containers
|
||||
```
|
||||
|
||||
**Default SSHD options** enforce: `PermitRootLogin no`, `PasswordAuthentication no`, `X11Forwarding no`, `AllowTcpForwarding no`, `MaxAuthTries 4`, and post-quantum KEX (mlkem768x25519-sha256 on OpenSSH 9.9+). Override per-option:
|
||||
|
||||
```yaml
|
||||
cis:
|
||||
sshd_options:
|
||||
- { option: X11Forwarding, value: "yes" }
|
||||
- { option: AllowTcpForwarding, value: "yes" }
|
||||
```
|
||||
|
||||
Note: providing `sshd_options` replaces the entire list. Copy the defaults from `roles/cis/defaults/main.yml` and modify as needed.
|
||||
|
||||
### 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 | Partition device path (required for physical data disks) |
|
||||
| `mount.path` | string | Mount point (additional disks only) |
|
||||
| `mount.fstype`| string | `btrfs`, `ext4`, or `xfs` |
|
||||
| `mount.label` | string | Filesystem label |
|
||||
| `mount.opts` | string | Mount options (default: `defaults`) |
|
||||
|
||||
```yaml
|
||||
system:
|
||||
disks:
|
||||
- size: 80 # OS disk
|
||||
- size: 200 # Data disk
|
||||
mount:
|
||||
path: /data
|
||||
fstype: xfs
|
||||
label: DATA
|
||||
```
|
||||
|
||||
### 4.7 Advanced Partitioning Overrides
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------------------------ | ------------ | ---------------------------------------- |
|
||||
| `partitioning_efi_size_mib` | `512` | EFI system partition size in MiB |
|
||||
| `partitioning_boot_size_mib` | `1024` | Separate `/boot` size in MiB |
|
||||
| `partitioning_separate_boot` | auto-derived | Force a separate `/boot` partition |
|
||||
| `partitioning_boot_fs_fstype` | auto-derived | Filesystem for `/boot` |
|
||||
| `partitioning_use_full_disk` | `true` | Use remaining VG space for root LV |
|
||||
|
||||
**Swap sizing:** RAM >= 16GB gets swap = RAM/2. RAM < 16GB gets swap = max(RAM_GB, 2GB). Further capped to prevent over-allocation on small disks.
|
||||
|
||||
**LVM layout** (when not using btrfs): root, swap, and when CIS is enabled: `/home` (2-20GB, 10% of disk), `/var` (2GB), `/var/log` (2GB), `/var/log/audit` (1.5GB).
|
||||
|
||||
### 4.8 Cleanup Defaults
|
||||
|
||||
Post-install verification and recovery settings.
|
||||
|
||||
| Variable | Default | Description |
|
||||
| --------------------------- | ------- | ----------------------------------------------------- |
|
||||
| `cleanup_verify_boot` | `true` | Check VM accessibility after reboot |
|
||||
| `cleanup_boot_timeout` | `300` | Timeout in seconds for boot verification |
|
||||
| `cleanup_remove_on_failure` | `true` | Auto-remove VMs that fail to boot (created this run only) |
|
||||
|
||||
## 5. Execution Pipeline
|
||||
|
||||
Roles execute in this order:
|
||||
|
||||
1. **global_defaults** -- normalize inputs, validate, set OS flags
|
||||
2. **system_check** -- detect installer environment, verify live/non-prod target
|
||||
3. **virtualization** -- create VM (if virtual), attach disks, cloud-init
|
||||
4. **environment** -- prepare installer: mount ISO, configure repos, setup pacman
|
||||
5. **partitioning** -- create partitions, LVM, LUKS, mount filesystems
|
||||
6. **bootstrap** -- install base system and packages (OS-specific)
|
||||
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
|
||||
1. [Overview](#1-overview)
|
||||
2. [Global Variables](#2-global-variables)
|
||||
3. [Inventory Variables](#3-inventory-variables)
|
||||
4. [How to Use the Playbook](#4-how-to-use-the-playbook)
|
||||
- 4.1 [Prerequisites](#41-prerequisites)
|
||||
- 4.2 [Running the Playbook](#42-running-the-playbook)
|
||||
- 4.3 [Example Usage](#43-example-usage)
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The playbook uses the ArchLinux ISO as a foundational tool to provides an efficient and systematic method for the automatic deployment of a variety of Linux distributions on designated target systems. It ensures a standardized setup across different platforms, equipping each system with the essential configurations and software necessary for its designated role.
|
||||
|
||||
## 2. Global Variables
|
||||
|
||||
Global variables apply across your Ansible project and are loaded from `vars.yml` by default. These variables define common settings such as hypervisor connection details and the boot ISO path. They can be overridden by inventory variables for specific hosts or VMs if needed.
|
||||
|
||||
| Variable | Description | Example Value |
|
||||
|-----------------------|--------------------------------------------------------------------|-----------------------------------------|
|
||||
| `boot_iso` | Path to the boot ISO image. | `local-btrfs:iso/archlinux-x86_64.iso` |
|
||||
| `rhel_iso` | Path to the RHEL ISO file, required for RHEL 8 and RHEL 9. |`local-btrfs:iso/rhel-9.4-x86_64-dvd.iso`|
|
||||
| `hypervisor` | Type of hypervisor. | `libvirt`, `proxmox`, `vmware`, `none` |
|
||||
| `hypervisor_cluster` | Name of the hypervisor cluster. | `default-cluster` |
|
||||
| `hypervisor_node` | Hypervisor node name. | `node01` |
|
||||
| `hypervisor_password` | Password for hypervisor authentication. | `123456` |
|
||||
| `hypervisor_storage` | Storage identifier for VM disks. | `local-btrfs` |
|
||||
| `hypervisor_url` | URL/IP address for the hypervisor interface. | `192.168.0.2` |
|
||||
| `hypervisor_username` | Username for hypervisor authentication. | `root@pam` |
|
||||
| `install_drive` | Drive where the system will be installed. | `/dev/sda` |
|
||||
| `install_type` | Type of installation. | `virtual`, `physical` |
|
||||
| `vlan_name` (optional)| VLAN for the VM's network interface. | `vlan100` |
|
||||
|
||||
To protect sensitive information, such as passwords, API keys, and other confidential variables (e.g., `hypervisor_password`), **it is recommended to use Ansible Vault**.
|
||||
|
||||
## 3. Inventory Variables
|
||||
|
||||
Inventory variables are defined for individual hosts or VMs in the inventory file, allowing customization of settings such as the operating system, filesystem, and compliance with CIS benchmarks. These variables can be set globally and overridden for specific hosts or VMs.
|
||||
|
||||
| Variable | Description | Example Value |
|
||||
|-------------------------|-----------------------------------------------------------------------------------|----------------------------------------------------|
|
||||
| `cis` (optional) | Adjusts the installation to be CIS level 3 conformant. | `true`, `false` |
|
||||
| `filesystem` | Filesystem type for the VM's primary storage. | `btrfs`, `ext4`, `xfs` |
|
||||
| `hostname` | The hostname assigned to the virtual machine or system. | `vm01` |
|
||||
| `os` | Operating system to be installed on the VM. | `archlinux`, `almalinux`, `debian11`, `debian12`, `fedora`, `rhel8`, `rhel9`, `rocky`, `ubuntu`, `ubuntu-lts` |
|
||||
| `root_password` | Root password for the VM or system, used for initial setup or secure access. | `SecurePass123` |
|
||||
| `user_name` | Username for a user account within the VM, often used with cloud-init. | `adminuser` |
|
||||
| `user_password` | Password for the user account within the VM. | `UserPass123` |
|
||||
| `vm_ballo` (optional) | Ballooning memory size for the VM, used to adjust memory allocation dynamically. | `2048` |
|
||||
| `vm_cpus` | Number of CPU cores assigned to the virtual machine. | `4` |
|
||||
| `vm_dns` | DNS server IP address(es) for the virtual machine's network configuration. | `1.0.0.1`, `1.1.1.1` |
|
||||
| `vm_gw` | Default gateway IP address for the virtual machine's network configuration. | `192.168.0.1` |
|
||||
| `vm_id` | Unique identifier for the virtual machine. | `101` |
|
||||
| `vm_ip` | IP address assigned to the virtual machine. | `192.168.0.10` |
|
||||
| `vm_nm` (optional) | IP address netmask assigned to the virtual machine. | `255.255.255.0` |
|
||||
| `vm_nms` (optional) | IP address netmask assigned to the virtual machine. | `24` |
|
||||
| `vm_memory` | Amount of memory (in MB) allocated to the virtual machine. | `2048` |
|
||||
| `vm_nif` | Network interface type or identifier for the VM's network connection. | `vmbr0` |
|
||||
| `vm_path (optional)` | Path or folder where the VM configuration or related files will be stored. | `/var/lib/libvirt/images/` |
|
||||
| `vm_size` | Disk size allocated for the VM's primary storage (in GB). | `20` |
|
||||
|
||||
## 4. How to Use the Playbook
|
||||
|
||||
### 4.1 Prerequisites
|
||||
|
||||
Before running the playbook, ensure you have Ansible installed and configured correctly, and your inventory file is set up with the target systems defined.
|
||||
|
||||
### 4.2 Running the Playbook
|
||||
|
||||
Execute the playbook using the `ansible-playbook` command, ensuring that all necessary variables are defined, typically by specifying a `vars.yml` file containing the required configurations.
|
||||
|
||||
### 4.3 Example Usage
|
||||
|
||||
An effective way to use the playbook involves defining all necessary configurations within a `vars.yml` file. This file should include all relevant global variables tailored to your specific deployment requirements. Additionally, you should prepare an inventory file (`inventory.yml`) that lists all the hosts along with any specific inventory variables they might need. Then, you can run the playbook as follows:
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory.yml main.yml
|
||||
ansible-playbook -i inventory.yml main.yml -e @vars.yml
|
||||
ansible-playbook -i inventory.yml -e @vars.yml main.yml
|
||||
```
|
||||
|
||||
All credentials (`system.users`, `system.root.password`) must be defined in inventory or passed via `-e`.
|
||||
|
||||
Example inventory files are included:
|
||||
|
||||
- `inventory_example.yml` -- Proxmox virtual setup
|
||||
- `inventory_libvirt_example.yml` -- libvirt virtual setup
|
||||
- `inventory_baremetal_example.yml` -- bare-metal physical setup
|
||||
|
||||
## 7. Security
|
||||
|
||||
Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, user passwords in `system.users`, `system.root.password`).
|
||||
|
||||
## 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 command prompts Ansible to execute the `main.yml` playbook, applying configurations defined in both `vars.yml` and the inventory file.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[defaults]
|
||||
hash_behaviour = merge
|
||||
interpreter_python = auto_silent
|
||||
deprecation_warnings = False
|
||||
host_key_checking = False
|
||||
|
||||
[ssh_connection]
|
||||
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=30 -o ServerAliveCountMax=10
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.posix
|
||||
version: "2.1.0"
|
||||
- name: community.general
|
||||
version: "12.3.0"
|
||||
- name: community.libvirt
|
||||
version: "2.0.0"
|
||||
- name: community.crypto
|
||||
version: "3.1.0"
|
||||
- name: community.proxmox
|
||||
version: "1.5.0"
|
||||
- name: community.vmware
|
||||
version: "6.2.0"
|
||||
- name: vmware.vmware
|
||||
version: "2.7.0"
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
all:
|
||||
vars:
|
||||
hypervisor:
|
||||
type: "none"
|
||||
system:
|
||||
filesystem: "ext4"
|
||||
hosts:
|
||||
baremetal01.example.com:
|
||||
ansible_host: 10.0.0.162
|
||||
ansible_user: root
|
||||
ansible_password: "1234"
|
||||
ansible_become_password: "1234"
|
||||
system:
|
||||
type: "physical"
|
||||
os: "archlinux"
|
||||
name: "baremetal01.example.com"
|
||||
disks:
|
||||
- device: "/dev/sda"
|
||||
size: 120
|
||||
29
inventory_example.ini
Normal file
29
inventory_example.ini
Normal file
@@ -0,0 +1,29 @@
|
||||
[promox-kvm]
|
||||
192.168.122.10
|
||||
192.168.122.11
|
||||
|
||||
[promox-kvm:vars]
|
||||
vm_gw=192.168.122.1
|
||||
vm_dns=1.1.1.1
|
||||
|
||||
[192.168.122.10]
|
||||
hostname=proxy
|
||||
vm_id=300
|
||||
os=archlinux
|
||||
filesystem=btrfs
|
||||
vm_memory=2048
|
||||
vm_ballo=1024
|
||||
vm_cpus=2
|
||||
vm_size=5
|
||||
vm_nif=vmbr1
|
||||
|
||||
[192.168.122.11]
|
||||
hostname=database
|
||||
vm_id=101
|
||||
os=archlinux
|
||||
filesystem=btrfs
|
||||
vm_memory=6144
|
||||
vm_ballo=3072
|
||||
vm_cpus=4
|
||||
vm_size=40
|
||||
vm_nif=vmbr1
|
||||
@@ -1,133 +1,33 @@
|
||||
---
|
||||
all:
|
||||
vars:
|
||||
hypervisor:
|
||||
type: "proxmox"
|
||||
url: "pve01.example.com"
|
||||
username: "root@pam"
|
||||
password: "CHANGE_ME"
|
||||
node: "pve01"
|
||||
storage: "local-lvm"
|
||||
boot_iso: "local:iso/archlinux-x86_64.iso"
|
||||
hypervisor: 'proxmox'
|
||||
install_drive: '/dev/sda'
|
||||
cis: true
|
||||
children:
|
||||
proxmox:
|
||||
hosts:
|
||||
app01.example.com:
|
||||
ansible_host: 10.0.0.10
|
||||
system:
|
||||
filesystem: "btrfs"
|
||||
type: "virtual"
|
||||
os: "archlinux"
|
||||
name: "app01.example.com"
|
||||
id: 100
|
||||
cpus: 2
|
||||
memory: 4096
|
||||
balloon: 0
|
||||
network:
|
||||
bridge: "vmbr0"
|
||||
ip: 10.0.0.10
|
||||
prefix: 24
|
||||
gateway: 10.0.0.1
|
||||
dns:
|
||||
servers:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
search:
|
||||
- example.com
|
||||
disks:
|
||||
- size: 40
|
||||
- size: 80
|
||||
mount:
|
||||
path: /data
|
||||
fstype: xfs
|
||||
label: DATA
|
||||
opts: defaults
|
||||
users:
|
||||
- name: "ops"
|
||||
password: "CHANGE_ME"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
root:
|
||||
password: "CHANGE_ME"
|
||||
packages:
|
||||
- jq
|
||||
- tmux
|
||||
features:
|
||||
cis:
|
||||
enabled: false
|
||||
selinux:
|
||||
enabled: true
|
||||
firewall:
|
||||
enabled: true
|
||||
backend: "firewalld"
|
||||
toolkit: "nftables"
|
||||
ssh:
|
||||
enabled: true
|
||||
zstd:
|
||||
enabled: true
|
||||
swap:
|
||||
enabled: true
|
||||
banner:
|
||||
motd: true
|
||||
sudo: true
|
||||
chroot:
|
||||
tool: "arch-chroot"
|
||||
db01.example.com:
|
||||
ansible_host: 10.0.0.11
|
||||
rhel_iso: "local:iso/rhel-9.4-x86_64-dvd.iso"
|
||||
system:
|
||||
filesystem: "xfs"
|
||||
type: "virtual"
|
||||
os: "rhel"
|
||||
version: "9"
|
||||
name: "db01.example.com"
|
||||
id: 101
|
||||
cpus: 4
|
||||
memory: 8192
|
||||
network:
|
||||
bridge: "vmbr0"
|
||||
ip: 10.0.0.11
|
||||
prefix: 24
|
||||
gateway: 10.0.0.1
|
||||
dns:
|
||||
servers:
|
||||
- "1.1.1.1"
|
||||
- "1.0.0.1"
|
||||
disks:
|
||||
- size: 80
|
||||
- size: 200
|
||||
mount:
|
||||
path: /srv/data
|
||||
fstype: ext4
|
||||
users:
|
||||
- name: "dbadmin"
|
||||
password: "CHANGE_ME"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
root:
|
||||
password: "CHANGE_ME"
|
||||
luks:
|
||||
enabled: true
|
||||
passphrase: "CHANGE_ME"
|
||||
method: "keyfile"
|
||||
keysize: 128
|
||||
features:
|
||||
cis:
|
||||
enabled: true
|
||||
selinux:
|
||||
enabled: false
|
||||
firewall:
|
||||
enabled: false
|
||||
backend: "firewalld"
|
||||
toolkit: "nftables"
|
||||
ssh:
|
||||
enabled: true
|
||||
zstd:
|
||||
enabled: true
|
||||
swap:
|
||||
enabled: true
|
||||
banner:
|
||||
motd: true
|
||||
sudo: true
|
||||
chroot:
|
||||
tool: "arch-chroot"
|
||||
promox-kvm:
|
||||
hosts:
|
||||
192.168.122.10:
|
||||
hostname: proxy
|
||||
vm_id: 100
|
||||
os: archlinux
|
||||
filesystem: btrfs
|
||||
vm_memory: "2048"
|
||||
vm_ballo: "1024"
|
||||
vm_cpus: "2"
|
||||
vm_size: "5"
|
||||
vm_nif: vmbr1
|
||||
vm_gw: 192.168.122.1
|
||||
vm_dns: 1.1.1.1
|
||||
192.168.122.11:
|
||||
hostname: database
|
||||
vm_id: 101
|
||||
os: rhel9
|
||||
filesystem: xfs
|
||||
vm_memory: "6144"
|
||||
vm_ballo: "3072"
|
||||
vm_cpus: "4"
|
||||
vm_size: "40"
|
||||
vm_nif: vmbr1
|
||||
vm_gw: 192.168.122.1
|
||||
vm_dns: 1.1.1.1
|
||||
rhel_iso: "local-btrfs:iso/rhel-9.4-x86_64-dvd.iso"
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
---
|
||||
all:
|
||||
vars:
|
||||
hypervisor:
|
||||
type: "libvirt"
|
||||
url: "localhost"
|
||||
username: ""
|
||||
password: ""
|
||||
host: ""
|
||||
storage: "default"
|
||||
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
|
||||
children:
|
||||
libvirt:
|
||||
hosts:
|
||||
web01.local:
|
||||
ansible_host: 192.168.122.20
|
||||
system:
|
||||
filesystem: "ext4"
|
||||
type: "virtual"
|
||||
os: "debian"
|
||||
version: "12"
|
||||
name: "web01.local"
|
||||
cpus: 2
|
||||
memory: 2048
|
||||
network:
|
||||
bridge: "default"
|
||||
ip: 192.168.122.20
|
||||
prefix: 24
|
||||
gateway: 192.168.122.1
|
||||
dns:
|
||||
servers:
|
||||
- 1.1.1.1
|
||||
search:
|
||||
- lab.local
|
||||
path: "/var/lib/libvirt/images"
|
||||
disks:
|
||||
- size: 30
|
||||
- size: 80
|
||||
mount:
|
||||
path: /var/www
|
||||
fstype: xfs
|
||||
users:
|
||||
- name: "web"
|
||||
password: "CHANGE_ME"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
root:
|
||||
password: "CHANGE_ME"
|
||||
packages:
|
||||
- nginx
|
||||
- curl
|
||||
features:
|
||||
firewall:
|
||||
enabled: true
|
||||
backend: "ufw"
|
||||
toolkit: "nftables"
|
||||
db01.local:
|
||||
ansible_host: 192.168.122.21
|
||||
rhel_iso: "/var/lib/libvirt/images/rhel-9.4-x86_64-dvd.iso"
|
||||
system:
|
||||
filesystem: "xfs"
|
||||
type: "virtual"
|
||||
os: "rhel"
|
||||
version: "9"
|
||||
name: "db01.local"
|
||||
cpus: 4
|
||||
memory: 4096
|
||||
network:
|
||||
bridge: "default"
|
||||
ip: 192.168.122.21
|
||||
prefix: 24
|
||||
gateway: 192.168.122.1
|
||||
dns:
|
||||
servers:
|
||||
- 9.9.9.9
|
||||
search:
|
||||
- example.com
|
||||
disks:
|
||||
- size: 60
|
||||
- size: 120
|
||||
mount:
|
||||
path: /data
|
||||
fstype: ext4
|
||||
users:
|
||||
- name: "db"
|
||||
password: "CHANGE_ME"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
root:
|
||||
password: "CHANGE_ME"
|
||||
luks:
|
||||
enabled: true
|
||||
passphrase: "CHANGE_ME"
|
||||
method: "keyfile"
|
||||
features:
|
||||
firewall:
|
||||
enabled: false
|
||||
backend: "firewalld"
|
||||
toolkit: "nftables"
|
||||
compute01.local:
|
||||
ansible_host: 192.168.122.22
|
||||
system:
|
||||
filesystem: "btrfs"
|
||||
type: "virtual"
|
||||
os: "fedora"
|
||||
version: "41"
|
||||
name: "compute01.local"
|
||||
cpus: 8
|
||||
memory: 8192
|
||||
network:
|
||||
bridge: "default"
|
||||
ip: 192.168.122.22
|
||||
prefix: 24
|
||||
gateway: 192.168.122.1
|
||||
dns:
|
||||
servers:
|
||||
- "1.1.1.1"
|
||||
- "1.0.0.1"
|
||||
disks:
|
||||
- size: 80
|
||||
- size: 200
|
||||
mount:
|
||||
path: /data
|
||||
fstype: btrfs
|
||||
users:
|
||||
- name: "compute"
|
||||
password: "CHANGE_ME"
|
||||
keys:
|
||||
- "ssh-ed25519 AAAA..."
|
||||
root:
|
||||
password: "CHANGE_ME"
|
||||
features:
|
||||
cis:
|
||||
enabled: true
|
||||
220
main.yml
220
main.yml
@@ -1,150 +1,88 @@
|
||||
---
|
||||
# Bootstrap pipeline — role execution order:
|
||||
# 1. global_defaults — normalize + validate system/hypervisor/disk input
|
||||
# 2. system_check — pre-flight hardware/environment safety checks
|
||||
# 3. virtualization — create VM on hypervisor (libvirt/proxmox/vmware/xen)
|
||||
# 4. environment — detect live ISO, configure installer network, install tools
|
||||
# 5. partitioning — partition disk, create FS, LUKS, LVM, mount everything
|
||||
# 6. bootstrap — debootstrap/pacstrap/dnf install the target OS into /mnt
|
||||
# 7. configuration — users, network, encryption, fstab, bootloader, services
|
||||
# 8. cis — CIS hardening (optional, per system.features.cis.enabled)
|
||||
# 9. cleanup — unmount, remove cloud-init artifacts, reboot/shutdown
|
||||
- name: Create and configure VMs
|
||||
hosts: "{{ bootstrap_target | default('all') }}"
|
||||
strategy: free # noqa: run-once[play]
|
||||
hosts: all
|
||||
strategy: free
|
||||
gather_facts: false
|
||||
become: true
|
||||
vars_prompt:
|
||||
- name: user_name
|
||||
prompt: |
|
||||
What is your username?
|
||||
private: false
|
||||
|
||||
- name: user_password
|
||||
prompt: |
|
||||
What is your password?
|
||||
confirm: true
|
||||
|
||||
- name: root_password
|
||||
prompt: |
|
||||
What is your root password?
|
||||
confirm: true
|
||||
vars_files: vars.yml
|
||||
pre_tasks:
|
||||
- name: Load global defaults
|
||||
ansible.builtin.import_role:
|
||||
name: global_defaults
|
||||
|
||||
- name: Perform safety checks
|
||||
ansible.builtin.import_role:
|
||||
name: system_check
|
||||
|
||||
tasks:
|
||||
- name: Bootstrap pipeline
|
||||
block:
|
||||
- name: Record that no pre-existing VM was found
|
||||
ansible.builtin.set_fact:
|
||||
_vm_absent_before_bootstrap: true
|
||||
|
||||
- name: Create virtual machine
|
||||
when: system_cfg.type == "virtual"
|
||||
ansible.builtin.include_role:
|
||||
name: virtualization
|
||||
public: true
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
|
||||
- name: Configure environment
|
||||
ansible.builtin.include_role:
|
||||
name: environment
|
||||
public: true
|
||||
|
||||
- name: Partition disks
|
||||
ansible.builtin.include_role:
|
||||
name: partitioning
|
||||
public: true
|
||||
vars:
|
||||
partitioning_boot_partition_suffix: 1
|
||||
partitioning_main_partition_suffix: 2
|
||||
|
||||
- name: Install base system
|
||||
ansible.builtin.include_role:
|
||||
name: bootstrap
|
||||
public: true
|
||||
|
||||
- name: Apply system configuration
|
||||
ansible.builtin.include_role:
|
||||
name: configuration
|
||||
public: 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: Delete VM on bootstrap failure
|
||||
when:
|
||||
- _vm_absent_before_bootstrap | default(false) | bool
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
- system_cfg.type == "virtual"
|
||||
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 (virtualization_vm_created_in_run | default(false))
|
||||
else 'VM was not created in this run (kept).' }}
|
||||
|
||||
post_tasks:
|
||||
- name: Set post-reboot connection flags
|
||||
- name: Set ansible_python_interpreter
|
||||
when: os | lower in ["almalinux", "rhel9", "rhel8", "rocky"]
|
||||
ansible.builtin.set_fact:
|
||||
post_reboot_can_connect: >-
|
||||
{{
|
||||
(ansible_connection | default('ssh')) != 'ssh'
|
||||
or ((system_cfg.network.ip | default('') | string | length) > 0)
|
||||
or (
|
||||
system_cfg.type == 'physical'
|
||||
and (ansible_host | default('') | string | length) > 0
|
||||
)
|
||||
}}
|
||||
|
||||
- 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
|
||||
when:
|
||||
- post_reboot_can_connect | bool
|
||||
no_log: true
|
||||
vars:
|
||||
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
||||
ansible.builtin.set_fact:
|
||||
ansible_connection: ssh
|
||||
ansible_host: "{{ system_cfg.network.ip }}"
|
||||
ansible_port: 22
|
||||
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_python_interpreter: /usr/bin/python3
|
||||
|
||||
- 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: Set SSH Access
|
||||
when: hypervisor != "vmware"
|
||||
ansible.builtin.set_fact:
|
||||
ansible_user: "{{ user_name }}"
|
||||
ansible_password: "{{ user_password }}"
|
||||
ansible_become_password: "{{ user_password }}"
|
||||
ansible_ssh_extra_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
|
||||
|
||||
- name: Install post-reboot packages
|
||||
when:
|
||||
- post_reboot_can_connect | bool
|
||||
- system_cfg.packages is defined
|
||||
- system_cfg.packages | length > 0
|
||||
ansible.builtin.package:
|
||||
name: "{{ system_cfg.packages }}"
|
||||
state: present
|
||||
- name: Validate variables
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- hypervisor in ["libvirt", "proxmox", "vmware", "none"]
|
||||
- filesystem in ["btrfs", "ext4", "xfs"]
|
||||
- install_drive is defined
|
||||
- os in ["archlinux", "almalinux", "debian11", "debian12", "fedora", "rhel8", "rhel9", "rocky", "ubuntu", "ubuntu-lts"]
|
||||
- os not in ["rhel8", "rhel9"] or rhel_iso is defined
|
||||
- (filesystem == "btrfs" and (vm_size | int) >= 10) or (filesystem != "btrfs" and (vm_size | int) >= 20)
|
||||
- (vm_size | float) >= ((vm_memory | float / 1024 >= 16.0) | ternary((vm_memory | float / 2048), [vm_memory | float / 1024, 4.0] | max) + 16)
|
||||
fail_msg: Invalid input specified, please try again.
|
||||
|
||||
- name: Set connection
|
||||
when: hypervisor == "vmware"
|
||||
ansible.builtin.set_fact:
|
||||
ansible_connection: vmware_tools
|
||||
|
||||
roles:
|
||||
- role: virtualization
|
||||
when: install_type == "virtual"
|
||||
become: false
|
||||
vars:
|
||||
ansible_connection: local
|
||||
|
||||
- role: environment
|
||||
vars:
|
||||
ansible_connection: "{{ 'vmware_tools' if hypervisor == 'vmware' else 'ssh' }}"
|
||||
|
||||
- role: partitioning
|
||||
vars:
|
||||
boot_partition_suffix: 1
|
||||
main_partition_suffix: 2
|
||||
|
||||
- role: bootstrap
|
||||
|
||||
- role: configuration
|
||||
|
||||
- role: cis
|
||||
when: cis | bool
|
||||
|
||||
- role: cleanup
|
||||
when: install_type == "virtual"
|
||||
vars:
|
||||
ansible_connection: local
|
||||
|
||||
tasks:
|
||||
- name: Reboot system
|
||||
when: hypervisor == "proxmox"
|
||||
ansible.builtin.command: reboot
|
||||
failed_when: false
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
- name: Molecule converge placeholder
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Skip destructive provisioning in Molecule
|
||||
ansible.builtin.debug:
|
||||
msg: "Molecule scenario is lint-only; run main.yml against disposable hosts."
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
driver:
|
||||
name: delegated
|
||||
platforms:
|
||||
- name: localhost
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
converge: converge.yml
|
||||
inventory:
|
||||
host_vars:
|
||||
localhost:
|
||||
ansible_connection: local
|
||||
lint:
|
||||
name: ansible-lint
|
||||
verifier:
|
||||
name: ansible
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
- name: Molecule verify placeholder
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Verify placeholder
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- true
|
||||
@@ -1,15 +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
|
||||
alpine: alpine.yml
|
||||
archlinux: archlinux.yml
|
||||
debian: debian.yml
|
||||
fedora: _dnf_family.yml
|
||||
opensuse: opensuse.yml
|
||||
rocky: _dnf_family.yml
|
||||
rhel: rhel.yml
|
||||
ubuntu: ubuntu.yml
|
||||
ubuntu-lts: ubuntu.yml
|
||||
void: void.yml
|
||||
@@ -1,48 +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({}) }}"
|
||||
ansible.builtin.set_fact:
|
||||
_desktop_groups: "{{ _de_config.groups | default([]) }}"
|
||||
_desktop_packages: "{{ _de_config.packages | default([]) }}"
|
||||
|
||||
- name: Validate desktop environment is supported
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (_desktop_groups | length > 0) or (_desktop_packages | length > 0)
|
||||
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 }}
|
||||
--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 }}
|
||||
--setopt=install_weak_deps=False install -y {{ _desktop_packages | join(' ') }}
|
||||
Debian: >-
|
||||
{{ chroot_command }} apt install -y {{ _desktop_packages | join(' ') }}
|
||||
Archlinux: >-
|
||||
pacstrap /mnt {{ _desktop_packages | join(' ') }}
|
||||
Suse: >-
|
||||
{{ chroot_command }} zypper install -y {{ _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 }} --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 }} --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,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
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap Alpine Linux
|
||||
vars:
|
||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
||||
_base_packages: "{{ _config.base | join(' ') }}"
|
||||
_extra_packages: >-
|
||||
{{
|
||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
||||
| reject('equalto', '')
|
||||
| join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Install Alpine Linux base
|
||||
ansible.builtin.command: >
|
||||
apk --root /mnt --no-cache add {{ _base_packages }}
|
||||
register: bootstrap_alpine_bootstrap_result
|
||||
changed_when: bootstrap_alpine_bootstrap_result.rc == 0
|
||||
|
||||
- name: Install extra packages
|
||||
when: _extra_packages | trim | length > 0
|
||||
ansible.builtin.command: >
|
||||
apk --root /mnt add {{ _extra_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,14 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap ArchLinux
|
||||
vars:
|
||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
||||
bootstrap_archlinux_packages: >-
|
||||
{{
|
||||
((_config.base | default([])) + (_config.conditional | default([])))
|
||||
| reject('equalto', '')
|
||||
| list
|
||||
}}
|
||||
ansible.builtin.command: >-
|
||||
pacstrap /mnt {{ bootstrap_archlinux_packages | join(' ') }}
|
||||
register: bootstrap_result
|
||||
changed_when: bootstrap_result.rc == 0
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap Debian System
|
||||
vars:
|
||||
bootstrap_debian_release: >-
|
||||
{{
|
||||
'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 'sid' if (os_version | string) == 'unstable'
|
||||
else 'trixie'
|
||||
}}
|
||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
||||
bootstrap_debian_base_csv: "{{ (['ca-certificates'] + _config.base) | unique | join(',') }}"
|
||||
bootstrap_debian_extra_args: >-
|
||||
{{
|
||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
||||
| reject('equalto', '')
|
||||
| join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Validate Debian package configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _config is mapping
|
||||
- _config.base is sequence
|
||||
- _config.extra is sequence
|
||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
||||
quiet: true
|
||||
|
||||
- name: Install Debian base system
|
||||
ansible.builtin.command: >-
|
||||
debootstrap --include={{ bootstrap_debian_base_csv }}
|
||||
{{ bootstrap_debian_release }} /mnt {{ system_cfg.mirror }}
|
||||
register: bootstrap_debian_base_result
|
||||
changed_when: bootstrap_debian_base_result.rc == 0
|
||||
|
||||
- name: Write bootstrap sources.list
|
||||
ansible.builtin.template:
|
||||
src: debian.sources.list.j2
|
||||
dest: /mnt/etc/apt/sources.list
|
||||
mode: "0644"
|
||||
|
||||
- name: Configure apt performance tuning
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
||||
content: |
|
||||
Acquire::Retries "3";
|
||||
Acquire::http::Pipeline-Depth "10";
|
||||
APT::Install-Recommends "false";
|
||||
mode: "0644"
|
||||
|
||||
- name: Update package lists
|
||||
ansible.builtin.command: "{{ chroot_command }} apt update"
|
||||
register: bootstrap_debian_update_result
|
||||
changed_when: bootstrap_debian_update_result.rc == 0
|
||||
|
||||
- name: Upgrade all packages to latest versions
|
||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
||||
register: bootstrap_debian_upgrade_result
|
||||
changed_when: "'0 upgraded' not in bootstrap_debian_upgrade_result.stdout"
|
||||
|
||||
- name: Install extra packages
|
||||
when: bootstrap_debian_extra_args | trim | length > 0
|
||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
||||
register: bootstrap_debian_extra_result
|
||||
changed_when: bootstrap_debian_extra_result.rc == 0
|
||||
|
||||
- name: Remove unnecessary packages
|
||||
ansible.builtin.command: "{{ chroot_command }} apt remove -y libcups2 libavahi-common3 libavahi-common-data"
|
||||
register: bootstrap_debian_remove_result
|
||||
changed_when: bootstrap_debian_remove_result.rc == 0
|
||||
@@ -1,46 +1,106 @@
|
||||
---
|
||||
- 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 }}"
|
||||
- name: Include Packages
|
||||
ansible.builtin.include_vars:
|
||||
file: packages.yml
|
||||
name: role_packages
|
||||
|
||||
- name: Run OS-specific bootstrap process
|
||||
vars:
|
||||
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
|
||||
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
||||
block:
|
||||
- name: Bootstrap ArchLinux
|
||||
when: os | lower == 'archlinux'
|
||||
ansible.builtin.command: pacstrap /mnt {{ role_packages.archlinux | join(' ') }} --asexplicit
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Install desktop environment packages
|
||||
when: system_cfg.features.desktop.enabled | bool
|
||||
ansible.builtin.include_tasks: _desktop.yml
|
||||
- name: Bootstrap Debian System
|
||||
when: os | lower in ['debian11', 'debian12']
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- debootstrap --include={{ role_packages[os].base | join(',') }} {{ 'bullseye' if os == 'debian11' else 'bookworm' }}
|
||||
/mnt http://deb.debian.org/debian/
|
||||
- arch-chroot /mnt apt install -y {{ role_packages[os].extra | join(' ') }}
|
||||
- arch-chroot /mnt apt remove -y libcups2 libavahi-common3 libavahi-common-data
|
||||
|
||||
- name: Ensure chroot uses live environment DNS
|
||||
ansible.builtin.file:
|
||||
src: /run/NetworkManager/resolv.conf
|
||||
dest: /mnt/etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
- name: Bootstrap Ubuntu System
|
||||
when: os | lower in ['ubuntu', 'ubuntu-lts']
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- debootstrap --include={{ role_packages[os].base | join(',') }} {{ 'oracular' if os == 'ubuntu' else 'noble' }}
|
||||
/mnt http://archive.ubuntu.com/ubuntu/
|
||||
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
|
||||
- arch-chroot /mnt sed -i '1s|$| universe|' /etc/apt/sources.list
|
||||
- arch-chroot /mnt apt update -y
|
||||
- arch-chroot /mnt apt install -y {{ role_packages[os].extra | join(' ') }}
|
||||
|
||||
- name: Bootstrap AlmaLinux 9
|
||||
when: os | lower == 'almalinux'
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- dnf --releasever=9 --best --repo=alma-baseos --installroot=/mnt --setopt=install_weak_deps=False groupinstall -y base core
|
||||
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
|
||||
- arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False install -y {{ role_packages.almalinux | join(' ') }}
|
||||
|
||||
- name: Bootstrap Fedora 41
|
||||
when: os | lower == 'fedora'
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- dnf --releasever=41 --best --repo=fedora --repo=fedora-updates
|
||||
--installroot=/mnt --setopt=install_weak_deps=False groupinstall -y critical-path-base core
|
||||
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
|
||||
- arch-chroot /mnt dnf --releasever=41 --setopt=install_weak_deps=False install -y {{ role_packages.fedora | join(' ') }}
|
||||
- arch-chroot /mnt dnf reinstall -y kernel-core
|
||||
|
||||
- name: Bootstrap RockyLinux 9
|
||||
when: os | lower == 'rocky'
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- dnf --releasever=9 --best --repo=rocky-baseos --installroot=/mnt
|
||||
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
||||
groupinstall -y base core
|
||||
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
|
||||
- arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False install -y {{ role_packages.rocky | join(' ') }}
|
||||
|
||||
- name: Bootstrap RHEL System
|
||||
when: os | lower in ['rhel8', 'rhel9']
|
||||
block:
|
||||
- name: Install base packages in chroot environment
|
||||
ansible.builtin.command: >-
|
||||
dnf --releasever={{ '8' if os == 'rhel8' else '9' }} --repo={{ os | lower }}-baseos
|
||||
--installroot=/mnt
|
||||
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
||||
groupinstall -y base core
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Prepare chroot environment
|
||||
ansible.builtin.shell: |
|
||||
ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
|
||||
mkdir -p /mnt/usr/local/install/redhat/dvd
|
||||
mount --bind /usr/local/install/redhat/dvd /mnt/usr/local/install/redhat/dvd
|
||||
arch-chroot /mnt rpm --rebuilddb
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Copy RHEL repo file into chroot environment
|
||||
ansible.builtin.copy:
|
||||
src: /etc/yum.repos.d/{{ os | lower }}.repo
|
||||
dest: /mnt/etc/yum.repos.d/{{ os | lower }}.repo
|
||||
mode: '0644'
|
||||
remote_src: true
|
||||
|
||||
- name: Install additional packages in chroot
|
||||
ansible.builtin.command: >-
|
||||
arch-chroot /mnt dnf --releasever={{ '8' if os == 'rhel8' else '9' }}
|
||||
--setopt=install_weak_deps=False install -y {{ role_packages[os] | join(' ') }}
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap openSUSE
|
||||
vars:
|
||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
||||
_base_patterns: "{{ _config.base | join(' ') }}"
|
||||
_extra_packages: >-
|
||||
{{
|
||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
||||
| reject('equalto', '')
|
||||
| join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Install openSUSE base patterns
|
||||
ansible.builtin.command: >
|
||||
zypper --root /mnt --non-interactive install -t pattern {{ _base_patterns }}
|
||||
register: bootstrap_opensuse_base_result
|
||||
changed_when: bootstrap_opensuse_base_result.rc == 0
|
||||
|
||||
- name: Install extra packages
|
||||
when: _extra_packages | trim | length > 0
|
||||
ansible.builtin.command: >
|
||||
zypper --root /mnt --non-interactive install {{ _extra_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,57 +0,0 @@
|
||||
---
|
||||
- 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:
|
||||
- name: Install base packages in chroot environment
|
||||
ansible.builtin.command: >-
|
||||
dnf --releasever={{ os_version_major }} --best {{ _rhel_repos }}
|
||||
--installroot=/mnt
|
||||
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
|
||||
groupinstall -y {{ _rhel_groups }}
|
||||
register: bootstrap_result
|
||||
changed_when: bootstrap_result.rc == 0
|
||||
failed_when:
|
||||
- bootstrap_result.rc != 0
|
||||
- "'grub2-common' not in (bootstrap_result.stderr | default(''))"
|
||||
|
||||
- name: Ensure chroot RHEL DVD directory exists
|
||||
ansible.builtin.file:
|
||||
path: /mnt/usr/local/install/redhat/dvd
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Bind mount RHEL DVD into chroot
|
||||
ansible.posix.mount:
|
||||
src: /usr/local/install/redhat/dvd
|
||||
path: /mnt/usr/local/install/redhat/dvd
|
||||
fstype: none
|
||||
opts: bind
|
||||
state: ephemeral
|
||||
|
||||
- name: Rebuild RPM database inside chroot
|
||||
ansible.builtin.command: "{{ chroot_command }} rpm --rebuilddb"
|
||||
register: bootstrap_rpm_rebuild_result
|
||||
changed_when: bootstrap_rpm_rebuild_result.rc == 0
|
||||
|
||||
- name: Copy RHEL repo file into chroot environment
|
||||
ansible.builtin.copy:
|
||||
src: /etc/yum.repos.d/rhel.repo
|
||||
dest: /mnt/etc/yum.repos.d/redhat.repo
|
||||
mode: "0644"
|
||||
remote_src: true
|
||||
|
||||
- name: Install additional packages in chroot
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} dnf --releasever={{ os_version_major }} --best
|
||||
--setopt=install_weak_deps=False install -y {{ _rhel_extra }}
|
||||
register: bootstrap_result
|
||||
changed_when: bootstrap_result.rc == 0
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap Ubuntu System
|
||||
vars:
|
||||
# ubuntu = latest non-LTS, ubuntu-lts = latest LTS
|
||||
bootstrap_ubuntu_release_map:
|
||||
ubuntu: questing
|
||||
ubuntu-lts: noble
|
||||
bootstrap_ubuntu_release: "{{ bootstrap_ubuntu_release_map[os] | default('noble') }}"
|
||||
_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([])))
|
||||
| reject('equalto', '')
|
||||
| join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Validate Ubuntu package configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _config is mapping
|
||||
- _config.base is sequence
|
||||
- _config.extra is sequence
|
||||
fail_msg: "{{ bootstrap_var_key }} must be a dict with base/extra/conditional keys."
|
||||
quiet: true
|
||||
|
||||
- name: Install Ubuntu base system
|
||||
ansible.builtin.command: >-
|
||||
debootstrap
|
||||
--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||
--include={{ bootstrap_ubuntu_base_csv }}
|
||||
{{ bootstrap_ubuntu_release }} /mnt
|
||||
{{ system_cfg.mirror }}
|
||||
register: bootstrap_ubuntu_base_result
|
||||
changed_when: bootstrap_ubuntu_base_result.rc == 0
|
||||
|
||||
- name: Write bootstrap sources.list
|
||||
ansible.builtin.template:
|
||||
src: ubuntu.sources.list.j2
|
||||
dest: /mnt/etc/apt/sources.list
|
||||
mode: "0644"
|
||||
|
||||
- name: Configure apt performance tuning
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
||||
content: |
|
||||
Acquire::Retries "3";
|
||||
Acquire::http::Pipeline-Depth "10";
|
||||
APT::Install-Recommends "false";
|
||||
mode: "0644"
|
||||
|
||||
- name: Update package lists
|
||||
ansible.builtin.command: "{{ chroot_command }} apt update"
|
||||
register: bootstrap_ubuntu_update_result
|
||||
changed_when: bootstrap_ubuntu_update_result.rc == 0
|
||||
|
||||
- name: Upgrade all packages to latest versions
|
||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
||||
register: bootstrap_ubuntu_upgrade_result
|
||||
changed_when: "'0 upgraded' not in bootstrap_ubuntu_upgrade_result.stdout"
|
||||
|
||||
- name: Install extra packages
|
||||
when: bootstrap_ubuntu_extra_args | trim | length > 0
|
||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra_args }}"
|
||||
register: bootstrap_ubuntu_extra_result
|
||||
changed_when: bootstrap_ubuntu_extra_result.rc == 0
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
- name: Bootstrap Void Linux
|
||||
vars:
|
||||
_config: "{{ lookup('vars', bootstrap_var_key) }}"
|
||||
_base_packages: "{{ _config.base | join(' ') }}"
|
||||
_extra_packages: >-
|
||||
{{
|
||||
((_config.extra | default([])) + (_config.conditional | default([])))
|
||||
| reject('equalto', '')
|
||||
| join(' ')
|
||||
}}
|
||||
block:
|
||||
- name: Install Void Linux base
|
||||
ansible.builtin.command: >
|
||||
xbps-install -Sy -r /mnt -R https://repo-default.voidlinux.org/current {{ _base_packages }}
|
||||
register: bootstrap_void_base_result
|
||||
changed_when: bootstrap_void_base_result.rc == 0
|
||||
|
||||
- name: Install extra packages
|
||||
when: _extra_packages | trim | length > 0
|
||||
ansible.builtin.command: >
|
||||
xbps-install -Su -r /mnt {{ _extra_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.mirror %}
|
||||
{% set components = 'main contrib non-free' ~ (' non-free-firmware' if (os_version | string) not in ['10', '11'] else '') %}
|
||||
|
||||
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.mirror %}
|
||||
{% 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,149 +0,0 @@
|
||||
---
|
||||
# Per-family desktop environment package definitions.
|
||||
# Keyed by os_family -> environment -> groups (dnf groupinstall) / packages.
|
||||
# Kept intentionally minimal: base DE + essential tools, no full suites.
|
||||
bootstrap_desktop_packages:
|
||||
RedHat:
|
||||
gnome:
|
||||
groups:
|
||||
- workstation-product-environment
|
||||
packages: []
|
||||
kde:
|
||||
groups: []
|
||||
packages:
|
||||
- plasma-desktop
|
||||
- plasma-nm
|
||||
- plasma-pa
|
||||
- plasma-systemmonitor
|
||||
- sddm
|
||||
- konsole
|
||||
- dolphin
|
||||
- kate
|
||||
- kscreen
|
||||
- kde-gtk-config
|
||||
- xdg-user-dirs
|
||||
- xdg-desktop-portal-kde
|
||||
- bluez
|
||||
- pipewire
|
||||
- wireplumber
|
||||
xfce:
|
||||
groups:
|
||||
- xfce-desktop-environment
|
||||
packages:
|
||||
- lightdm
|
||||
Debian:
|
||||
gnome:
|
||||
groups: []
|
||||
packages:
|
||||
- gnome-core
|
||||
- gdm3
|
||||
- gnome-tweaks
|
||||
- xdg-user-dirs
|
||||
kde:
|
||||
groups: []
|
||||
packages:
|
||||
- plasma-desktop
|
||||
- plasma-nm
|
||||
- plasma-pa
|
||||
- sddm
|
||||
- konsole
|
||||
- dolphin
|
||||
- kate
|
||||
- kscreen
|
||||
- xdg-user-dirs
|
||||
- xdg-desktop-portal-kde
|
||||
- bluez
|
||||
- pipewire
|
||||
- wireplumber
|
||||
xfce:
|
||||
groups: []
|
||||
packages:
|
||||
- xfce4
|
||||
- xfce4-goodies
|
||||
- lightdm
|
||||
- xdg-user-dirs
|
||||
Archlinux:
|
||||
gnome:
|
||||
groups: []
|
||||
packages:
|
||||
- gnome
|
||||
- gdm
|
||||
- xdg-user-dirs
|
||||
kde:
|
||||
groups: []
|
||||
packages:
|
||||
- plasma-desktop
|
||||
- plasma-nm
|
||||
- plasma-pa
|
||||
- sddm
|
||||
- konsole
|
||||
- dolphin
|
||||
- kate
|
||||
- kscreen
|
||||
- kde-gtk-config
|
||||
- xdg-user-dirs
|
||||
- xdg-desktop-portal-kde
|
||||
- bluez
|
||||
- pipewire
|
||||
- wireplumber
|
||||
xfce:
|
||||
groups: []
|
||||
packages:
|
||||
- xfce4
|
||||
- xfce4-goodies
|
||||
- lightdm
|
||||
- xdg-user-dirs
|
||||
sway:
|
||||
groups: []
|
||||
packages:
|
||||
- sway
|
||||
- waybar
|
||||
- foot
|
||||
- wofi
|
||||
- greetd
|
||||
- xdg-user-dirs
|
||||
- xdg-desktop-portal-wlr
|
||||
- bluez
|
||||
- pipewire
|
||||
- wireplumber
|
||||
hyprland:
|
||||
groups: []
|
||||
packages:
|
||||
- hyprland
|
||||
- kitty
|
||||
- wofi
|
||||
- waybar
|
||||
- ly
|
||||
- xdg-user-dirs
|
||||
- xdg-desktop-portal-hyprland
|
||||
- polkit-kde-agent
|
||||
- qt5-wayland
|
||||
- qt6-wayland
|
||||
- bluez
|
||||
- pipewire
|
||||
- wireplumber
|
||||
Suse:
|
||||
gnome:
|
||||
groups: []
|
||||
packages:
|
||||
- patterns-gnome-gnome_basic
|
||||
- gdm
|
||||
- xdg-user-dirs
|
||||
kde:
|
||||
groups: []
|
||||
packages:
|
||||
- patterns-kde-kde_plasma
|
||||
- sddm
|
||||
- xdg-user-dirs
|
||||
|
||||
# Display manager auto-detection from desktop environment.
|
||||
bootstrap_desktop_dm_map:
|
||||
gnome: gdm
|
||||
kde: sddm
|
||||
xfce: lightdm
|
||||
sway: greetd
|
||||
hyprland: ly@tty2
|
||||
cinnamon: lightdm
|
||||
mate: lightdm
|
||||
lxqt: sddm
|
||||
budgie: gdm
|
||||
@@ -1,400 +0,0 @@
|
||||
---
|
||||
# Feature-gated packages shared across all distros.
|
||||
# Arch has special nftables handling and composes this differently.
|
||||
bootstrap_common_conditional: >-
|
||||
{{
|
||||
(
|
||||
(['firewalld'] if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['iptables'] if system_cfg.features.firewall.toolkit == 'iptables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['nftables'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['cryptsetup', 'tpm2-tools'] if system_cfg.luks.enabled | bool else [])
|
||||
+ (['qemu-guest-agent'] if hypervisor_type in ['libvirt', 'proxmox'] else [])
|
||||
+ (['open-vm-tools'] if hypervisor_type == 'vmware' else [])
|
||||
)
|
||||
}}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-OS package definitions: base (rootfs/group install), extra (post-base),
|
||||
# conditional (feature/version-gated, appended by task files).
|
||||
# DNF-based distros also carry repos (dnf --repo) and use base as group names.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
bootstrap_rhel:
|
||||
repos:
|
||||
- "rhel{{ os_version_major }}-baseos"
|
||||
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_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_almalinux:
|
||||
repos:
|
||||
- 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: >-
|
||||
{{
|
||||
(['dbus-daemon'] if (os_version_major | default('10') | int) >= 9 else [])
|
||||
+ (['dhcp-client'] if (os_version_major | default('10') | int) < 10 else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_rocky:
|
||||
repos:
|
||||
- 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
|
||||
- telnet
|
||||
- tmux
|
||||
- util-linux-core
|
||||
- vim
|
||||
- wget
|
||||
- zram-generator
|
||||
- zstd
|
||||
conditional: >-
|
||||
{{
|
||||
(['dhcp-client'] if (os_version_major | default('9') | int) < 10 else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_fedora:
|
||||
repos:
|
||||
- fedora
|
||||
- fedora-updates
|
||||
base:
|
||||
- critical-path-base
|
||||
- core
|
||||
extra:
|
||||
- bat
|
||||
- bind-utils
|
||||
- btrfs-progs
|
||||
- cronie
|
||||
- dhcp-client
|
||||
- duf
|
||||
- efibootmgr
|
||||
- entr
|
||||
- fish
|
||||
- fzf
|
||||
- glibc-langpack-de
|
||||
- glibc-langpack-en
|
||||
- grub2
|
||||
- grub2-efi
|
||||
- htop
|
||||
- iperf3
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nc
|
||||
- nfs-utils
|
||||
- nfsv4-client-utils
|
||||
- polkit
|
||||
- ppp
|
||||
- python3
|
||||
- ripgrep
|
||||
- shim
|
||||
- tmux
|
||||
- vim-default-editor
|
||||
- wget
|
||||
- zoxide
|
||||
- zram-generator
|
||||
- zstd
|
||||
conditional: "{{ bootstrap_common_conditional }}"
|
||||
|
||||
bootstrap_debian:
|
||||
base:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- cryptsetup-initramfs
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- locales
|
||||
- logrotate
|
||||
- lvm2
|
||||
- openssh-server
|
||||
- python3
|
||||
- xfsprogs
|
||||
extra:
|
||||
- apparmor-utils
|
||||
- bat
|
||||
- chrony
|
||||
- curl
|
||||
- entr
|
||||
- fish
|
||||
- fzf
|
||||
- htop
|
||||
- jq
|
||||
- libpam-pwquality
|
||||
- linux-image-amd64
|
||||
- lrzsz
|
||||
- mtr
|
||||
- ncdu
|
||||
- needrestart
|
||||
- net-tools
|
||||
- network-manager
|
||||
- python-is-python3
|
||||
- ripgrep
|
||||
- rsync
|
||||
- screen
|
||||
- sudo
|
||||
- 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_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_ubuntu:
|
||||
base:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- cryptsetup-initramfs
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- initramfs-tools
|
||||
- linux-image-generic
|
||||
- locales
|
||||
- logrotate
|
||||
- lvm2
|
||||
- openssh-server
|
||||
- python3
|
||||
- xfsprogs
|
||||
extra:
|
||||
- apparmor-utils
|
||||
- bash-completion
|
||||
- bat
|
||||
- chrony
|
||||
- curl
|
||||
- dnsutils
|
||||
- duf
|
||||
- entr
|
||||
- eza
|
||||
- fdupes
|
||||
- fio
|
||||
- fish
|
||||
- fzf
|
||||
- htop
|
||||
- jq
|
||||
- libpam-pwquality
|
||||
- lrzsz
|
||||
- mtr
|
||||
- ncdu
|
||||
- ncurses-term
|
||||
- needrestart
|
||||
- net-tools
|
||||
- network-manager
|
||||
- python-is-python3
|
||||
- ripgrep
|
||||
- rsync
|
||||
- screen
|
||||
- software-properties-common
|
||||
- sudo
|
||||
- syslog-ng
|
||||
- systemd-zram-generator
|
||||
- tcpd
|
||||
- traceroute
|
||||
- util-linux-extra
|
||||
- vim
|
||||
- wget
|
||||
- yq
|
||||
- zoxide
|
||||
- zstd
|
||||
conditional: >-
|
||||
{{
|
||||
(['tldr'] if (os_version | default('') | string | length) > 0 else [])
|
||||
+ (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_archlinux:
|
||||
base:
|
||||
- base
|
||||
- btrfs-progs
|
||||
- cronie
|
||||
- dhcpcd
|
||||
- efibootmgr
|
||||
- fastfetch
|
||||
- fish
|
||||
- fzf
|
||||
- grub
|
||||
- htop
|
||||
- libpwquality
|
||||
- linux
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- lsof
|
||||
- lvm2
|
||||
- ncdu
|
||||
- networkmanager
|
||||
- nfs-utils
|
||||
- ppp
|
||||
- python
|
||||
- reflector
|
||||
- rsync
|
||||
- sudo
|
||||
- tldr
|
||||
- tmux
|
||||
- vim
|
||||
- zram-generator
|
||||
extra: []
|
||||
conditional: >-
|
||||
{{
|
||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
|
||||
+ (['sbctl'] if system_cfg.features.secure_boot.enabled | bool else [])
|
||||
+ (bootstrap_common_conditional | reject('equalto', 'nftables') | list)
|
||||
}}
|
||||
|
||||
bootstrap_alpine:
|
||||
base:
|
||||
- alpine-base
|
||||
extra:
|
||||
- btrfs-progs
|
||||
- chrony
|
||||
- curl
|
||||
- e2fsprogs
|
||||
- linux-lts
|
||||
- logrotate
|
||||
- lvm2
|
||||
- python3
|
||||
- rsync
|
||||
- sudo
|
||||
- util-linux
|
||||
- vim
|
||||
- xfsprogs
|
||||
conditional: >-
|
||||
{{
|
||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_opensuse:
|
||||
base:
|
||||
- patterns-base-base
|
||||
extra:
|
||||
- btrfs-progs
|
||||
- chrony
|
||||
- curl
|
||||
- e2fsprogs
|
||||
- glibc-locale
|
||||
- kernel-default
|
||||
- logrotate
|
||||
- lvm2
|
||||
- NetworkManager
|
||||
- python3
|
||||
- rsync
|
||||
- sudo
|
||||
- vim
|
||||
- xfsprogs
|
||||
conditional: >-
|
||||
{{
|
||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
|
||||
bootstrap_void:
|
||||
base:
|
||||
- base-system
|
||||
- void-repo-nonfree
|
||||
extra:
|
||||
- btrfs-progs
|
||||
- chrony
|
||||
- curl
|
||||
- dhcpcd
|
||||
- e2fsprogs
|
||||
- logrotate
|
||||
- lvm2
|
||||
- python3
|
||||
- rsync
|
||||
- sudo
|
||||
- vim
|
||||
- xfsprogs
|
||||
conditional: >-
|
||||
{{
|
||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ bootstrap_common_conditional
|
||||
}}
|
||||
307
roles/bootstrap/vars/packages.yml
Normal file
307
roles/bootstrap/vars/packages.yml
Normal file
@@ -0,0 +1,307 @@
|
||||
---
|
||||
almalinux:
|
||||
- bind-utils
|
||||
- dbus-daemon
|
||||
- dhcp-client
|
||||
- efibootmgr
|
||||
- glibc-langpack-de
|
||||
- glibc-langpack-en
|
||||
- grub2
|
||||
- grub2-efi
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nc
|
||||
- nfs-utils
|
||||
- nfsv4-client-utils
|
||||
- open-vm-tools
|
||||
- ppp
|
||||
- shim
|
||||
- telnet
|
||||
- vim
|
||||
- wget
|
||||
- zstd
|
||||
|
||||
archlinux:
|
||||
- base
|
||||
- btrfs-progs
|
||||
- cronie
|
||||
- dhcpcd
|
||||
- efibootmgr
|
||||
- firewalld
|
||||
- fish
|
||||
- grub
|
||||
- htop
|
||||
- libpwquality
|
||||
- linux
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- lsof
|
||||
- lvm2
|
||||
- ncdu
|
||||
- neofetch
|
||||
- networkmanager
|
||||
- nfs-utils
|
||||
- openssh
|
||||
- open-vm-tools
|
||||
- ppp
|
||||
- prometheus-node-exporter
|
||||
- python-psycopg2
|
||||
- qemu-guest-agent
|
||||
- reflector
|
||||
- rsync
|
||||
- screen
|
||||
- sudo
|
||||
- vim
|
||||
- wireguard-tools
|
||||
- zram-generator
|
||||
|
||||
debian11:
|
||||
base:
|
||||
- apparmor-utils
|
||||
- btrfs-progs
|
||||
- chrony
|
||||
- cron
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- linux-image-amd64
|
||||
- locales
|
||||
- logrotate
|
||||
- lvm2
|
||||
- net-tools
|
||||
- openssh-server
|
||||
- python3
|
||||
- sudo
|
||||
- xfsprogs
|
||||
|
||||
extra:
|
||||
- curl
|
||||
- firewalld
|
||||
- fish
|
||||
- htop
|
||||
- libpam-pwquality
|
||||
- lrzsz
|
||||
- ncdu
|
||||
- neofetch
|
||||
- network-manager
|
||||
- open-vm-tools
|
||||
- python-is-python3
|
||||
- rsync
|
||||
- screen
|
||||
- software-properties-common
|
||||
- syslog-ng
|
||||
- tcpd
|
||||
- vim
|
||||
- wget
|
||||
- zstd
|
||||
|
||||
debian12:
|
||||
base:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- linux-image-amd64
|
||||
- locales
|
||||
- logrotate
|
||||
- lvm2
|
||||
- xfsprogs
|
||||
|
||||
extra:
|
||||
- apparmor-utils
|
||||
- chrony
|
||||
- curl
|
||||
- firewalld
|
||||
- fish
|
||||
- htop
|
||||
- libpam-pwquality
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- ncdu
|
||||
- neofetch
|
||||
- net-tools
|
||||
- network-manager
|
||||
- open-vm-tools
|
||||
- openssh-server
|
||||
- python-is-python3
|
||||
- python3
|
||||
- rsync
|
||||
- screen
|
||||
- software-properties-common
|
||||
- sudo
|
||||
- systemd-zram-generator
|
||||
- syslog-ng
|
||||
- tcpd
|
||||
- vim
|
||||
- wget
|
||||
- zstd
|
||||
|
||||
fedora:
|
||||
- bind-utils
|
||||
- btrfs-progs
|
||||
- cronie
|
||||
- dhcp-client
|
||||
- efibootmgr
|
||||
- glibc-langpack-de
|
||||
- glibc-langpack-en
|
||||
- grub2
|
||||
- grub2-efi
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nc
|
||||
- nfs-utils
|
||||
- nfsv4-client-utils
|
||||
- open-vm-tools
|
||||
- polkit
|
||||
- ppp
|
||||
- shim
|
||||
- telnet
|
||||
- vim-default-editor
|
||||
- wget
|
||||
- zram-generator
|
||||
- zstd
|
||||
|
||||
rhel8:
|
||||
- dhcp-client
|
||||
- efibootmgr
|
||||
- grub2
|
||||
- grub2-efi-x64
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nfs-utils
|
||||
- open-vm-tools
|
||||
- shim
|
||||
- telnet
|
||||
- vim
|
||||
- zstd
|
||||
|
||||
rhel9:
|
||||
- dhcp-client
|
||||
- efibootmgr
|
||||
- grub2
|
||||
- grub2-efi
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nfs-utils
|
||||
- open-vm-tools
|
||||
- shim
|
||||
- telnet
|
||||
- vim
|
||||
- zram-generator
|
||||
- zstd
|
||||
|
||||
rocky:
|
||||
- bind-utils
|
||||
- dbus-daemon
|
||||
- dhcp-client
|
||||
- efibootmgr
|
||||
- glibc-langpack-de
|
||||
- glibc-langpack-en
|
||||
- grub2
|
||||
- grub2-efi
|
||||
- lrzsz
|
||||
- lvm2
|
||||
- nc
|
||||
- nfs-utils
|
||||
- nfsv4-client-utils
|
||||
- open-vm-tools
|
||||
- ppp
|
||||
- shim
|
||||
- telnet
|
||||
- util-linux-core
|
||||
- vim
|
||||
- wget
|
||||
- zram-generator
|
||||
- zstd
|
||||
|
||||
ubuntu:
|
||||
base:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- initramfs-tools
|
||||
- linux-image-generic
|
||||
- locales
|
||||
- lvm2
|
||||
- xfsprogs
|
||||
|
||||
extra:
|
||||
- apparmor-utils
|
||||
- bash-completion
|
||||
- chrony
|
||||
- curl
|
||||
- dnsutils
|
||||
- firewalld
|
||||
- fish
|
||||
- htop
|
||||
- libpam-pwquality
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- ncdu
|
||||
- net-tools
|
||||
- network-manager
|
||||
- open-vm-tools
|
||||
- openssh-server
|
||||
- python-is-python3
|
||||
- python3
|
||||
- rsync
|
||||
- screen
|
||||
- software-properties-common
|
||||
- sudo
|
||||
- syslog-ng
|
||||
- systemd-zram-generator
|
||||
- tcpd
|
||||
- vim
|
||||
- wget
|
||||
- zstd
|
||||
|
||||
ubuntu-lts:
|
||||
base:
|
||||
- btrfs-progs
|
||||
- cron
|
||||
- gnupg
|
||||
- grub-efi
|
||||
- grub-efi-amd64-signed
|
||||
- grub2-common
|
||||
- initramfs-tools
|
||||
- linux-image-generic
|
||||
- locales
|
||||
- lvm2
|
||||
- xfsprogs
|
||||
|
||||
extra:
|
||||
- apparmor-utils
|
||||
- bash-completion
|
||||
- chrony
|
||||
- curl
|
||||
- dnsutils
|
||||
- firewalld
|
||||
- fish
|
||||
- htop
|
||||
- libpam-pwquality
|
||||
- logrotate
|
||||
- lrzsz
|
||||
- ncdu
|
||||
- net-tools
|
||||
- network-manager
|
||||
- open-vm-tools
|
||||
- openssh-server
|
||||
- python-is-python3
|
||||
- python3
|
||||
- rsync
|
||||
- screen
|
||||
- software-properties-common
|
||||
- sudo
|
||||
- syslog-ng
|
||||
- systemd-zram-generator
|
||||
- tcpd
|
||||
- vim
|
||||
- wget
|
||||
- zstd
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
# User-facing API: override via top-level `cis` dict in inventory.
|
||||
# Merged with these defaults in _normalize.yml → cis_cfg.
|
||||
cis_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
|
||||
tmout: 900
|
||||
umask: "077"
|
||||
umask_profile: "027"
|
||||
faillock_deny: 5
|
||||
faillock_unlock_time: 900
|
||||
password_remember: 5
|
||||
|
||||
# Platform-specific binary names for CIS permission targets
|
||||
cis_fusermount_binary: "{{ 'fusermount3' if is_rhel | default(false) | bool else 'fusermount' }}"
|
||||
cis_write_binary: "{{ 'write' if is_rhel | default(false) | bool else 'wall' }}"
|
||||
|
||||
cis: {}
|
||||
|
||||
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/cron.weekly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.monthly", mode: "0700" }
|
||||
- { path: "/mnt/etc/cron.d", mode: "0700" }
|
||||
- { path: "/mnt/etc/crontab", mode: "0600" }
|
||||
- { path: "/mnt/etc/logrotate.conf", mode: "0644" }
|
||||
- { path: "/mnt/usr/sbin/pppd", mode: "0754" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755" }
|
||||
- { path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755" }
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
- name: Normalize CIS input
|
||||
ansible.builtin.set_fact:
|
||||
cis_enabled: "{{ cis is defined and (cis is mapping or cis | bool) }}"
|
||||
cis_input: "{{ cis if cis is mapping else {} }}"
|
||||
|
||||
- name: Normalize CIS configuration
|
||||
when: cis_enabled and cis_cfg is not defined
|
||||
ansible.builtin.set_fact:
|
||||
cis_cfg: "{{ cis_defaults | combine(cis_input, recursive=True) }}"
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
- name: Ensure the Default UMASK is Set Correctly
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/mnt/etc/profile"
|
||||
regexp: "^(\\s*)umask\\s+\\d+"
|
||||
line: "umask {{ cis_cfg.umask_profile }}"
|
||||
|
||||
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
|
||||
- name: Prevent Login to Accounts With Empty Password
|
||||
ansible.builtin.replace:
|
||||
dest: "{{ item }}"
|
||||
regexp: "\\s*nullok"
|
||||
replace: ""
|
||||
loop: >-
|
||||
{{
|
||||
['/mnt/etc/pam.d/system-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,14 +0,0 @@
|
||||
---
|
||||
# Fedora ships its own crypto-policies preset and update-crypto-policies
|
||||
# behaves differently; applying DEFAULT:NO-SHA1 can break package signing.
|
||||
- name: Configure System Cryptography Policy
|
||||
when: os in (os_family_rhel | difference(['fedora']))
|
||||
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1"
|
||||
register: cis_crypto_policy_result
|
||||
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
|
||||
|
||||
- name: Mask Systemd Services
|
||||
ansible.builtin.command: >
|
||||
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} bluetooth rpcbind
|
||||
register: cis_mask_services_result
|
||||
changed_when: "'Created symlink' in cis_mask_services_result.stderr"
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
- name: Ensure files exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: touch
|
||||
mode: "0600"
|
||||
loop:
|
||||
- /mnt/etc/at.allow
|
||||
- /mnt/etc/cron.allow
|
||||
- /mnt/etc/hosts.allow
|
||||
- /mnt/etc/hosts.deny
|
||||
|
||||
- name: Ensure files do not exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /mnt/etc/at.deny
|
||||
- /mnt/etc/cron.deny
|
||||
@@ -1,20 +1,183 @@
|
||||
---
|
||||
- name: Normalize CIS configuration
|
||||
ansible.builtin.import_tasks: _normalize.yml
|
||||
|
||||
- name: Apply CIS hardening
|
||||
when: cis_enabled
|
||||
- name: Configurationg System for CIS conformity
|
||||
block:
|
||||
- name: Include CIS hardening tasks
|
||||
ansible.builtin.include_tasks: "{{ cis_task }}"
|
||||
- name: Disable Kernel Modules
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/modprobe.d/cis.conf
|
||||
mode: '0644'
|
||||
content: |
|
||||
CIS LVL 3 Restrictions
|
||||
install freevxfs /bin/true
|
||||
install jffs2 /bin/true
|
||||
install hfs /bin/true
|
||||
install hfsplus /bin/true
|
||||
install squashfs /bin/true
|
||||
install udf /bin/true
|
||||
install usb-storage /bin/true
|
||||
|
||||
install dccp /bin/true
|
||||
install sctp /bin/true
|
||||
install rds /bin/true
|
||||
install tipc /bin/true
|
||||
|
||||
- name: Create USB Rules
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
|
||||
mode: '0644'
|
||||
content: |
|
||||
By default, disable all.
|
||||
ACTION=="add", SUBSYSTEMS=="usb", TEST=="authorized_default", ATTR{authorized_default}="0"
|
||||
|
||||
Enable hub devices.
|
||||
ACTION=="add", ATTR{bDeviceClass}=="09", TEST=="authorized", ATTR{authorized}="1"
|
||||
|
||||
Enables keyboard devices
|
||||
ACTION=="add", ATTR{product}=="*[Kk]eyboard*", TEST=="authorized", ATTR{authorized}="1"
|
||||
|
||||
PS2-USB converter
|
||||
ACTION=="add", ATTR{product}=="*Thinnet TM*", TEST=="authorized", ATTR{authorized}="1"
|
||||
|
||||
- name: Create a consolidated sysctl configuration file
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/sysctl.d/10-cis.conf
|
||||
mode: '0644'
|
||||
content: |
|
||||
## CIS Sysctl configurations
|
||||
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.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
|
||||
|
||||
# - name: Adjust login.defs
|
||||
# replace:
|
||||
# path: /mnt/etc/login.defs
|
||||
# regexp: "{{ item.regexp }}"
|
||||
# replace: "{{ item.replace }}"
|
||||
# loop:
|
||||
# - { regexp: '^PASS_MAX_DAYS.*', replace: 'PASS_MAX_DAYS 90' }
|
||||
# - { regexp: '^PASS_MIN_DAYS.*', replace: 'PASS_MIN_DAYS 7' }
|
||||
# - { regexp: '^UMASK.*', replace: 'UMASK 027' }
|
||||
|
||||
- name: Ensure files exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: touch
|
||||
mode: "0600"
|
||||
loop:
|
||||
- modules.yml
|
||||
- sysctl.yml
|
||||
- auth.yml
|
||||
- crypto.yml
|
||||
- files.yml
|
||||
- security_lines.yml
|
||||
- permissions.yml
|
||||
- sshd.yml
|
||||
loop_control:
|
||||
loop_var: cis_task
|
||||
- /mnt/etc/at.allow
|
||||
- /mnt/etc/cron.allow
|
||||
- /mnt/etc/hosts.allow
|
||||
- /mnt/etc/hosts.deny
|
||||
|
||||
- name: Add Security related lines into config files
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item.path }}"
|
||||
line: "{{ item.content }}"
|
||||
loop:
|
||||
- { path: /mnt/etc/security/limits.conf, content: "* hard core 0" }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: minlen = 14 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: dcredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: ucredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: ocredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, content: lcredit = -1 }
|
||||
- { path: '/mnt/etc/{{ "bashrc" if os in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"] else "bash.bashrc" }}', content: umask 077 }
|
||||
- { path: '/mnt/etc/{{ "bashrc" if os in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"] else "bash.bashrc" }}', content: export TMOUT=3000 }
|
||||
- { 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" }
|
||||
- { path: /mnt/etc/pam.d/su, content: auth required pam_wheel.so }
|
||||
- { path: '/mnt/etc/{{ "pam.d/common-auth" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
|
||||
else "authselect/system-auth" if os == "fedora" else "pam.d/system-auth" }}',
|
||||
content: auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900 }
|
||||
- { path: '/mnt/etc/{{ "pam.d/common-account" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "authselect/system-auth"
|
||||
if os == "fedora" else "pam.d/system-auth" }}', content: account required pam_faillock.so }
|
||||
- { path: '/mnt/etc/pam.d/{{ "common-password" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "passwd" }}',
|
||||
content: "password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5" }
|
||||
- { path: /mnt/etc/hosts.deny, content: "ALL: ALL" }
|
||||
- { path: /mnt/etc/hosts.allow, content: "sshd: ALL" }
|
||||
|
||||
- name: Set permissions for various files and directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
owner: "{{ item.owner | default(omit) }}"
|
||||
group: "{{ item.group | default(omit) }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: >
|
||||
{{ [
|
||||
{ "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/cron.weekly", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.monthly", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/cron.d", "mode": "0700" },
|
||||
{ "path": "/mnt/etc/crontab", "mode": "0600" },
|
||||
{ "path": "/mnt/etc/logrotate.conf", "mode": "0644" },
|
||||
{ "path": "/mnt/usr/sbin/pppd", "mode": "0754" } if os not in ["rhel8", "rhel9"] else None,
|
||||
{ "path": "/mnt/usr/bin/" + ("fusermount3" if os in ["almalinux", "archlinux", "debian12", "fedora", "rhel9", "rocky"]
|
||||
else "fusermount"), "mode": "755" },
|
||||
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian11" else "write"), "mode": "755" }
|
||||
] | reject("none") }}
|
||||
|
||||
- name: Adjust SSHD config
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
regexp: ^\s*#?{{ item.option }}\s+.*$
|
||||
line: "{{ item.option }} {{ item.value }}"
|
||||
with_items:
|
||||
- { 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: ChallengeResponseAuthentication, value: "no" }
|
||||
- { option: GatewayPorts, value: "no" }
|
||||
- { option: X11Forwarding, value: "no" }
|
||||
- { option: PermitUserEnvironment, value: "no" }
|
||||
- { option: ClientAliveInterval, value: "300" }
|
||||
- { option: ClientAliveCountMax, value: "0" }
|
||||
- { option: PermitTunnel, value: "no" }
|
||||
- { option: Banner, value: /etc/issue.net }
|
||||
|
||||
- name: Append CIS Specific configurations to sshd_config
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
line: |2-
|
||||
|
||||
## CIS Specific
|
||||
Protocol 2
|
||||
|
||||
### Ciphers and keying ###
|
||||
RekeyLimit 512M 6h
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
|
||||
Ciphers chacha20-poly1305@openssh.com,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,hmac-sha2-512,hmac-sha2-256
|
||||
###########################
|
||||
|
||||
AllowStreamLocalForwarding no
|
||||
PermitUserRC no
|
||||
|
||||
AllowUsers *
|
||||
AllowGroups *
|
||||
DenyUsers nobody
|
||||
DenyGroups nobody
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
- name: Disable Kernel Modules
|
||||
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:
|
||||
dest: /mnt/etc/modprobe.d/cis.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
# CIS LVL 3 Restrictions
|
||||
{% for mod in cis_modules_all %}
|
||||
install {{ mod }}{{ ' ' * (16 - mod | length) }}/bin/false
|
||||
{% endfor %}
|
||||
|
||||
- name: Remove old USB rules file
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
|
||||
state: absent
|
||||
|
||||
- name: Create USB rules
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.rules
|
||||
mode: "0644"
|
||||
content: |
|
||||
# By default, disable all.
|
||||
ACTION=="add", SUBSYSTEMS=="usb", TEST=="authorized_default", ATTR{authorized_default}="0"
|
||||
# Enable hub devices.
|
||||
ACTION=="add", ATTR{bDeviceClass}=="09", TEST=="authorized", ATTR{authorized}="1"
|
||||
# Enable keyboard devices.
|
||||
ACTION=="add", ATTR{product}=="*[Kk]eyboard*", TEST=="authorized", ATTR{authorized}="1"
|
||||
# PS2-USB converter.
|
||||
ACTION=="add", ATTR{product}=="*Thinnet TM*", TEST=="authorized", ATTR{authorized}="1"
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
- name: Check CIS permission targets
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.path }}"
|
||||
loop: "{{ cis_permission_targets }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
register: cis_permission_stats
|
||||
changed_when: false
|
||||
|
||||
- name: Set permissions for existing targets
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.item.path }}"
|
||||
owner: "{{ item.item.owner | default(omit) }}"
|
||||
group: "{{ item.item.group | default(omit) }}"
|
||||
mode: "{{ item.item.mode }}"
|
||||
loop: "{{ cis_permission_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.path }}"
|
||||
when: item.stat.exists
|
||||
@@ -1,62 +0,0 @@
|
||||
---
|
||||
- name: Add Security related lines into config files
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item.path }}"
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.content }}"
|
||||
loop:
|
||||
- { path: /mnt/etc/security/limits.conf, regexp: '^\*\s+hard\s+core\s+', content: "* hard core 0" }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*minlen\s*=', content: "minlen = {{ cis_cfg.pwquality_minlen }}" }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*dcredit\s*=', content: dcredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*ucredit\s*=', content: ucredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*ocredit\s*=', content: ocredit = -1 }
|
||||
- { path: /mnt/etc/security/pwquality.conf, regexp: '^\s*#?\s*lcredit\s*=', content: lcredit = -1 }
|
||||
- path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*umask\s+\d+'
|
||||
content: "umask {{ cis_cfg.umask }}"
|
||||
- path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}'
|
||||
regexp: '^\s*(export\s+)?TMOUT='
|
||||
content: "export TMOUT={{ cis_cfg.tmout }}"
|
||||
- path: '/mnt/{{ "usr/lib/systemd/journald.conf" if is_rhel | bool else "etc/systemd/journald.conf" }}'
|
||||
regexp: '^\s*#?\s*Storage='
|
||||
content: Storage=persistent
|
||||
- path: /mnt/etc/sudoers
|
||||
regexp: '^\s*Defaults\s+logfile='
|
||||
content: Defaults logfile="/var/log/sudo.log"
|
||||
- path: /mnt/etc/pam.d/su
|
||||
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
|
||||
content: auth required pam_wheel.so
|
||||
- path: >-
|
||||
/mnt/etc/{{
|
||||
"pam.d/common-auth"
|
||||
if is_debian | bool
|
||||
else "authselect/system-auth"
|
||||
if os == "fedora"
|
||||
else "pam.d/system-auth"
|
||||
}}
|
||||
regexp: '^\s*auth\s+required\s+pam_faillock\.so'
|
||||
content: >-
|
||||
auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }}
|
||||
- path: >-
|
||||
/mnt/etc/{{
|
||||
"pam.d/common-account"
|
||||
if is_debian | bool
|
||||
else "authselect/system-auth"
|
||||
if os == "fedora"
|
||||
else "pam.d/system-auth"
|
||||
}}
|
||||
regexp: '^\s*account\s+required\s+pam_faillock\.so'
|
||||
content: account required pam_faillock.so
|
||||
- path: >-
|
||||
/mnt/etc/pam.d/{{
|
||||
"common-password"
|
||||
if is_debian | bool
|
||||
else "passwd"
|
||||
}}
|
||||
regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so'
|
||||
content: >-
|
||||
password [success=1 default=ignore] pam_unix.so obscure sha512 remember={{ cis_cfg.password_remember }}
|
||||
- { path: /mnt/etc/hosts.deny, regexp: '^ALL:\s*ALL', content: "ALL: ALL" }
|
||||
- { path: /mnt/etc/hosts.allow, regexp: '^sshd:\s*ALL', content: "sshd: ALL" }
|
||||
loop_control:
|
||||
label: "{{ item.content }}"
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
- name: Adjust SSHD config
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
regexp: ^\s*#?{{ item.option }}\s+.*$
|
||||
line: "{{ item.option }} {{ item.value }}"
|
||||
loop: "{{ cis_cfg.sshd_options }}"
|
||||
loop_control:
|
||||
label: "{{ item.option }}"
|
||||
|
||||
- name: Detect target OpenSSH version
|
||||
ansible.builtin.shell: >-
|
||||
set -o pipefail && {{ chroot_command }} ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9]+\.[0-9]+'
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: cis_sshd_openssh_version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Append CIS specific configurations to sshd_config
|
||||
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:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
marker: "# {mark} CIS SSH HARDENING"
|
||||
block: |-
|
||||
## CIS Specific
|
||||
### Ciphers and keying ###
|
||||
RekeyLimit 512M 6h
|
||||
KexAlgorithms {{ cis_sshd_kex | join(',') }}
|
||||
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
|
||||
###########################
|
||||
AllowStreamLocalForwarding no
|
||||
PermitUserRC no
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
- name: Create a consolidated sysctl configuration file
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/sysctl.d/10-cis.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
## CIS Sysctl configurations
|
||||
{% for key, value in cis_cfg.sysctl | dictsort %}
|
||||
{{ key }}={{ value }}
|
||||
{% endfor %}
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
# OS-specific binary names for CIS permission targets.
|
||||
# fusermount3 is the modern name; older distros still use 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'
|
||||
}}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
# Post-reboot verification
|
||||
cleanup_verify_boot: true
|
||||
cleanup_boot_timeout: 300
|
||||
cleanup_remove_on_failure: true
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
- name: Remove Archiso and cloud-init disks
|
||||
when: hypervisor_type == "libvirt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Read current VM XML definition
|
||||
community.libvirt.virt:
|
||||
command: get_xml
|
||||
name: "{{ hostname }}"
|
||||
register: cleanup_libvirt_get_xml
|
||||
changed_when: false
|
||||
|
||||
- name: Initialize cleaned VM XML
|
||||
ansible.builtin.set_fact:
|
||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
|
||||
|
||||
- name: Remove boot ISO device from VM XML (source match)
|
||||
when: boot_iso is defined and boot_iso | length > 0
|
||||
community.general.xml:
|
||||
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
||||
xpath: "/domain/devices/disk[contains(source/@file, '{{ boot_iso | basename }}')]"
|
||||
state: absent
|
||||
register: cleanup_libvirt_xml_strip_boot_source
|
||||
|
||||
- name: Update cleaned VM XML after removing boot ISO source match
|
||||
when: boot_iso is defined and boot_iso | length > 0
|
||||
ansible.builtin.set_fact:
|
||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
|
||||
|
||||
- name: Remove boot ISO device from VM XML (target fallback)
|
||||
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 }}"
|
||||
|
||||
- name: Remove cloud-init ISO device from VM XML (source match)
|
||||
community.general.xml:
|
||||
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
|
||||
xpath: "/domain/devices/disk[contains(source/@file, '{{ hostname }}-cloudinit.iso')]"
|
||||
state: absent
|
||||
register: cleanup_libvirt_xml_strip_cloudinit_source
|
||||
|
||||
- name: Update cleaned VM XML after removing cloud-init ISO source match
|
||||
ansible.builtin.set_fact:
|
||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
|
||||
|
||||
- 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
|
||||
ansible.builtin.set_fact:
|
||||
cleanup_libvirt_domain_xml_clean: >-
|
||||
{{
|
||||
cleanup_libvirt_domain_xml
|
||||
| replace('\ufeff', '')
|
||||
| regex_replace("(?is)<\\?xml[^>]*\\?>", "")
|
||||
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
|
||||
| trim
|
||||
}}
|
||||
|
||||
- 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
|
||||
community.libvirt.virt:
|
||||
command: define
|
||||
xml: "{{ cleanup_libvirt_domain_xml_clean }}"
|
||||
|
||||
- name: Remove cloud-init disk
|
||||
ansible.builtin.file:
|
||||
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
||||
state: absent
|
||||
|
||||
- name: Ensure VM is powered off before restart
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
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
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
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
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 300
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
@@ -1,8 +1,110 @@
|
||||
---
|
||||
- name: Cleanup physical install
|
||||
when: system_cfg.type == "physical"
|
||||
ansible.builtin.include_tasks: physical.yml
|
||||
- name: Setup Cleanup
|
||||
when: hypervisor == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Cleanup Setup Disks
|
||||
community.general.proxmox_disk:
|
||||
api_host: "{{ hypervisor_url }}"
|
||||
api_user: "{{ hypervisor_username }}"
|
||||
api_password: "{{ hypervisor_password }}"
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ vm_id }}"
|
||||
disk: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- ide0
|
||||
- ide2
|
||||
|
||||
- name: Cleanup virtual install
|
||||
when: system_cfg.type == "virtual"
|
||||
ansible.builtin.include_tasks: virtual.yml
|
||||
- name: Clean vCenter VM
|
||||
when: hypervisor == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Shutdown VM in vCenter
|
||||
when: hypervisor == "vmware"
|
||||
community.vmware.vmware_guest_powerstate:
|
||||
hostname: "{{ hypervisor_url }}"
|
||||
username: "{{ hypervisor_username }}"
|
||||
password: "{{ hypervisor_password }}"
|
||||
validate_certs: false
|
||||
datacenter: "{{ hypervisor_cluster }}"
|
||||
name: "{{ hostname }}"
|
||||
state: powered-off
|
||||
|
||||
- name: Remove CD-ROM from VM in vCenter
|
||||
when: hypervisor == "vmware"
|
||||
failed_when: false
|
||||
community.vmware.vmware_guest:
|
||||
hostname: "{{ hypervisor_url }}"
|
||||
username: "{{ hypervisor_username }}"
|
||||
password: "{{ hypervisor_password }}"
|
||||
validate_certs: false
|
||||
datacenter: "{{ hypervisor_cluster }}"
|
||||
name: "{{ hostname }}"
|
||||
cdrom:
|
||||
- controller_number: 0
|
||||
unit_number: 0
|
||||
controller_type: sata
|
||||
type: iso
|
||||
iso_path: "{{ boot_iso }}"
|
||||
state: absent
|
||||
- controller_number: 0
|
||||
unit_number: 1
|
||||
controller_type: sata
|
||||
type: iso
|
||||
iso_path: "{{ rhel_iso | default(omit) }}"
|
||||
state: absent
|
||||
|
||||
- name: Start VM in vCenter
|
||||
when: hypervisor == "vmware"
|
||||
community.vmware.vmware_guest_powerstate:
|
||||
hostname: "{{ hypervisor_url }}"
|
||||
username: "{{ hypervisor_username }}"
|
||||
password: "{{ hypervisor_password }}"
|
||||
validate_certs: false
|
||||
datacenter: "{{ hypervisor_cluster }}"
|
||||
name: "{{ hostname }}"
|
||||
state: powered-on
|
||||
|
||||
- name: Remove Archiso and cloud-init disks
|
||||
when: hypervisor == "libvirt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Stop the VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
state: shutdown
|
||||
|
||||
- name: Remove cloud-init disk
|
||||
ansible.builtin.file:
|
||||
path: "{{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}-cloudinit.iso"
|
||||
state: absent
|
||||
|
||||
- name: Get list of CD-ROM devices
|
||||
ansible.builtin.shell: set -o pipefail && virsh --connect qemu:///system domblklist {{ hostname }} --details | grep 'cdrom' | awk '{print $3}'
|
||||
changed_when: false
|
||||
register: cdrom_devices
|
||||
|
||||
- name: Wait for VM to spin down
|
||||
ansible.builtin.wait_for:
|
||||
timeout: 15
|
||||
|
||||
- name: Remove CD-ROM devices
|
||||
when: cdrom_devices.stdout_lines | length > 0
|
||||
ansible.builtin.command: virsh --connect qemu:///system detach-disk {{ hostname }} {{ item }} --persistent
|
||||
with_items: "{{ cdrom_devices.stdout_lines | select('ne', 'sdc') | list }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Start the VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
state: running
|
||||
|
||||
- name: Wait for VM to boot up
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 300
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
- name: Unmount installer mounts
|
||||
ansible.builtin.include_tasks: unmount.yml
|
||||
|
||||
- name: Trigger reboot into installed system
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- reboot
|
||||
async: 1
|
||||
poll: 0
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
ignore_unreachable: true
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
- name: Setup Cleanup
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_disk: "{{ _proxmox_auth }}"
|
||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
||||
block:
|
||||
- name: Cleanup Setup Disks
|
||||
community.proxmox.proxmox_disk:
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
disk: "{{ item }}"
|
||||
state: absent
|
||||
loop: >-
|
||||
{{
|
||||
['ide0', 'ide2']
|
||||
+ (['ide1'] if not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso') else [])
|
||||
}}
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Start the VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: restarted
|
||||
no_log: true
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
- name: Shutdown the VM
|
||||
become: true
|
||||
community.general.shutdown:
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
- name: Unmount Disks
|
||||
become: true
|
||||
block:
|
||||
- name: Disable Swap
|
||||
ansible.builtin.command: swapoff -a
|
||||
register: cleanup_swapoff_result
|
||||
changed_when: cleanup_swapoff_result.rc == 0
|
||||
|
||||
- name: Unmount /mnt if mounted
|
||||
ansible.builtin.command: umount -R /mnt
|
||||
register: cleanup_unmount_result
|
||||
changed_when: cleanup_unmount_result.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Verify /mnt is no longer mounted
|
||||
ansible.builtin.command: grep ' /mnt ' /proc/mounts
|
||||
until: cleanup_verify_unmount.rc != 0
|
||||
retries: 5
|
||||
delay: 5
|
||||
register: cleanup_verify_unmount
|
||||
changed_when: false
|
||||
failed_when: cleanup_verify_unmount.rc not in [0, 1]
|
||||
@@ -1,157 +0,0 @@
|
||||
---
|
||||
- name: Unmount installer mounts
|
||||
ansible.builtin.include_tasks: unmount.yml
|
||||
|
||||
- name: Shutdown installer environment
|
||||
ansible.builtin.include_tasks: shutdown.yml
|
||||
|
||||
- name: Cleanup hypervisor resources
|
||||
ansible.builtin.include_tasks: "{{ hypervisor_type }}.yml"
|
||||
|
||||
- name: Determine post-reboot connectivity
|
||||
ansible.builtin.set_fact:
|
||||
cleanup_post_reboot_can_connect: >-
|
||||
{{
|
||||
(
|
||||
post_reboot_can_connect
|
||||
if post_reboot_can_connect is defined
|
||||
else (
|
||||
(ansible_connection | default('ssh')) != 'ssh'
|
||||
or ((system_cfg.network.ip | default('') | string | length) > 0)
|
||||
or (
|
||||
system_cfg.type == 'physical'
|
||||
and (ansible_host | default('') | string | length) > 0
|
||||
)
|
||||
)
|
||||
) | bool
|
||||
}}
|
||||
|
||||
- name: Check VM accessibility after reboot
|
||||
when:
|
||||
- cleanup_verify_boot | bool
|
||||
- system_cfg.type == "virtual"
|
||||
- cleanup_post_reboot_can_connect | bool
|
||||
block:
|
||||
- name: Attempt to connect to VM
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: "{{ cleanup_boot_timeout }}"
|
||||
register: cleanup_vm_connection_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: VM failed to boot - initiate cleanup
|
||||
when:
|
||||
- cleanup_remove_on_failure | bool
|
||||
- cleanup_vm_connection_check is defined
|
||||
- cleanup_vm_connection_check.failed | bool
|
||||
- virtualization_vm_created_in_run | default(false) | bool
|
||||
block:
|
||||
- name: VM boot failure detected - removing VM
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
VM {{ hostname }} failed to boot after provisioning.
|
||||
This VM was created in the current playbook run and will be removed
|
||||
to prevent orphaned resources.
|
||||
|
||||
- name: Remove failed libvirt VM
|
||||
when: hypervisor_type == "libvirt"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Destroy libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
state: destroyed
|
||||
failed_when: false
|
||||
|
||||
- name: Undefine libvirt VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ hostname }}"
|
||||
command: undefine
|
||||
|
||||
- name: Remove libvirt VM disks
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ virtualization_libvirt_disks | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Remove libvirt cloud-init disk
|
||||
ansible.builtin.file:
|
||||
path: "{{ virtualization_libvirt_cloudinit_path }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove failed Proxmox VM
|
||||
when: hypervisor_type == "proxmox"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.proxmox.proxmox_kvm: "{{ _proxmox_auth_node }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Stop Proxmox VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: stopped
|
||||
|
||||
- name: Delete Proxmox VM
|
||||
community.proxmox.proxmox_kvm:
|
||||
name: "{{ hostname }}"
|
||||
vmid: "{{ system_cfg.id }}"
|
||||
state: absent
|
||||
unprivileged: false
|
||||
|
||||
- name: Remove failed VMware VM
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Power off VMware VM
|
||||
community.vmware.vmware_guest:
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path | default('/') }}"
|
||||
state: poweredoff
|
||||
|
||||
- name: Delete VMware VM
|
||||
community.vmware.vmware_guest:
|
||||
name: "{{ hostname }}"
|
||||
folder: "{{ system_cfg.path | default('/') }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove failed Xen VM
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
block:
|
||||
- name: Destroy Xen VM if running
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
- destroy
|
||||
- "{{ hostname }}"
|
||||
register: cleanup_xen_destroy
|
||||
failed_when: false
|
||||
changed_when: cleanup_xen_destroy.rc == 0
|
||||
|
||||
- name: Remove Xen VM disks
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ virtualization_xen_disks | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Remove Xen VM config file
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/xen-{{ hostname }}.cfg"
|
||||
state: absent
|
||||
|
||||
- name: VM cleanup completed
|
||||
ansible.builtin.debug:
|
||||
msg: VM {{ hostname }} has been successfully removed due to boot failure.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
- name: Clean vCenter VM
|
||||
when: hypervisor_type == "vmware"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
module_defaults:
|
||||
community.vmware.vmware_guest: "{{ _vmware_auth }}"
|
||||
vmware.vmware.vm_powerstate: "{{ _vmware_auth }}"
|
||||
no_log: true
|
||||
block:
|
||||
- name: Remove CD-ROM from VM in vCenter
|
||||
community.vmware.vmware_guest:
|
||||
name: "{{ hostname }}"
|
||||
cdrom: >-
|
||||
{{
|
||||
[
|
||||
{
|
||||
'controller_number': 0,
|
||||
'unit_number': 0,
|
||||
'controller_type': 'sata',
|
||||
'type': 'iso',
|
||||
'iso_path': boot_iso,
|
||||
'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.features.rhel_repo.source == 'iso'))
|
||||
else []
|
||||
)
|
||||
}}
|
||||
failed_when: false
|
||||
|
||||
- name: Start VM in vCenter
|
||||
vmware.vmware.vm_powerstate:
|
||||
name: "{{ hostname }}"
|
||||
state: powered-on
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
- name: Cleanup Xen installer media
|
||||
when: hypervisor_type == "xen"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
vars:
|
||||
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
||||
block:
|
||||
- name: Ensure Xen disk definitions exist
|
||||
ansible.builtin.include_tasks: ../../virtualization/tasks/_xen_disks.yml
|
||||
|
||||
- name: Render Xen VM configuration without installer media
|
||||
vars:
|
||||
xen_installer_media_enabled: false
|
||||
ansible.builtin.template:
|
||||
src: xen.cfg.j2
|
||||
dest: /tmp/xen-{{ hostname }}.cfg
|
||||
mode: "0644"
|
||||
|
||||
- name: Destroy Xen VM if running
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
- destroy
|
||||
- "{{ hostname }}"
|
||||
register: cleanup_xen_destroy
|
||||
failed_when: false
|
||||
changed_when: cleanup_xen_destroy.rc == 0
|
||||
|
||||
- name: Start Xen VM without installer media
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- xl
|
||||
- create
|
||||
- /tmp/xen-{{ hostname }}.cfg
|
||||
register: cleanup_xen_start_result
|
||||
changed_when: cleanup_xen_start_result.rc == 0
|
||||
|
||||
- name: Remove temporary Xen configuration file
|
||||
ansible.builtin.file:
|
||||
path: /tmp/xen-{{ hostname }}.cfg
|
||||
state: absent
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# Network configuration dispatch — maps OS name to the task file
|
||||
# that writes network config. Default (NetworkManager) applies to
|
||||
# all OSes not explicitly listed.
|
||||
configuration_network_task_map:
|
||||
alpine: network_alpine.yml
|
||||
void: network_void.yml
|
||||
@@ -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,66 +0,0 @@
|
||||
---
|
||||
- name: Configure MOTD
|
||||
when: system_cfg.features.banner.motd | bool
|
||||
block:
|
||||
- name: Create MOTD file
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
********************************************************************
|
||||
* AUTHORIZED ACCESS ONLY. ALL ACTIVITIES ARE MONITORED AND LOGGED. *
|
||||
********************************************************************
|
||||
dest: /mnt/etc/motd
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Remove other MOTD files
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /mnt/etc/motd.d/99-motd
|
||||
- /mnt/etc/motd.d/cockpit
|
||||
- /mnt/etc/motd.d/insights-client
|
||||
failed_when: false
|
||||
|
||||
- name: Create login banner
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ item }}"
|
||||
content: |
|
||||
**************************************************************
|
||||
* WARNING: Unauthorized access to this system is prohibited. *
|
||||
* All activities are monitored and logged. *
|
||||
* Disconnect immediately if you are not an authorized user. *
|
||||
**************************************************************
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- /mnt/etc/issue
|
||||
- /mnt/etc/issue.net
|
||||
|
||||
- name: Configure sudo banner
|
||||
when: system_cfg.features.banner.sudo | bool
|
||||
block:
|
||||
- name: Create sudo lecture file
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
I am Groot, and I know what I'm doing.
|
||||
dest: /mnt/etc/sudo_lecture
|
||||
mode: "0644"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Enable sudo lecture in sudoers
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/sudoers
|
||||
line: "{{ item }}"
|
||||
state: present
|
||||
create: true
|
||||
mode: "0440"
|
||||
owner: root
|
||||
group: root
|
||||
validate: "/usr/sbin/visudo --check --file=%s"
|
||||
loop:
|
||||
- "Defaults lecture=always"
|
||||
- "Defaults lecture_file=/etc/sudo_lecture"
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
- 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:
|
||||
- name: Install GRUB EFI binary
|
||||
when: _configuration_platform.grub_install
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} /usr/sbin/grub-install --target=x86_64-efi
|
||||
--efi-directory={{ partitioning_efi_mountpoint }}
|
||||
--bootloader-id={{ _efi_vendor }}
|
||||
--no-nvram
|
||||
register: configuration_bootloader_result
|
||||
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
|
||||
when: os == "archlinux" and system_cfg.filesystem != "btrfs"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/mkinitcpio.conf
|
||||
regexp: "^(HOOKS=.*block)(?!.*lvm2)(.*)"
|
||||
line: "\\1 lvm2\\2"
|
||||
backrefs: true
|
||||
|
||||
- name: Regenerate initramfs
|
||||
when: _configuration_platform.initramfs_cmd | length > 0
|
||||
ansible.builtin.command: "{{ chroot_command }} {{ _configuration_platform.initramfs_cmd }}"
|
||||
register: configuration_initramfs_result
|
||||
changed_when: configuration_initramfs_result.rc == 0
|
||||
|
||||
- name: Generate grub config (RedHat)
|
||||
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:
|
||||
_grub2_path: >-
|
||||
{{
|
||||
'/grub2'
|
||||
if (partitioning_separate_boot | bool)
|
||||
else ('/@/boot/grub2' if system_cfg.filesystem == 'btrfs' else '/boot/grub2')
|
||||
}}
|
||||
ansible.builtin.shell:
|
||||
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
|
||||
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,159 +0,0 @@
|
||||
---
|
||||
- name: Configure disk encryption
|
||||
when: system_cfg.luks.enabled | bool
|
||||
no_log: true
|
||||
vars:
|
||||
configuration_luks_passphrase: >-
|
||||
{{ system_cfg.luks.passphrase | string }}
|
||||
block:
|
||||
- name: Set LUKS configuration facts
|
||||
vars:
|
||||
_raw_pcrs: >-
|
||||
{{
|
||||
(
|
||||
system_cfg.luks.tpm2.pcrs
|
||||
if system_cfg.luks.tpm2.pcrs is string
|
||||
else (system_cfg.luks.tpm2.pcrs | map('string') | join('+'))
|
||||
)
|
||||
| string
|
||||
| replace(',', '+')
|
||||
| regex_replace('\\s+', '')
|
||||
| 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:
|
||||
configuration_luks_mapper_name: "{{ system_cfg.luks.mapper }}"
|
||||
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
||||
configuration_luks_device: "{{ partitioning_luks_device }}"
|
||||
configuration_luks_options: "{{ system_cfg.luks.options }}"
|
||||
configuration_luks_auto_method: >-
|
||||
{{
|
||||
(system_cfg.luks.auto | bool)
|
||||
| ternary(
|
||||
system_cfg.luks.method,
|
||||
'manual'
|
||||
)
|
||||
}}
|
||||
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
|
||||
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
||||
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
||||
configuration_luks_tpm2_token_lib: >-
|
||||
{{
|
||||
'/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
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- configuration_luks_uuid | length > 0
|
||||
fail_msg: LUKS UUID not available. Ensure partitioning ran before configuration.
|
||||
|
||||
- name: Validate LUKS passphrase for auto-decrypt
|
||||
when: configuration_luks_auto_method in ['tpm2', 'keyfile']
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- configuration_luks_passphrase | length > 0
|
||||
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
|
||||
no_log: true
|
||||
|
||||
- name: Detect TPM2 unlock method
|
||||
ansible.builtin.include_tasks: encryption/initramfs_detect.yml
|
||||
|
||||
- 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
|
||||
|
||||
- name: Configure LUKS keyfile auto-decrypt
|
||||
when: configuration_luks_auto_method == 'keyfile'
|
||||
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
|
||||
vars:
|
||||
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
|
||||
luks_option_list: >-
|
||||
{{
|
||||
(configuration_luks_options | trim).split(',')
|
||||
if configuration_luks_options | trim | length > 0
|
||||
else []
|
||||
}}
|
||||
luks_tpm2_option_list: >-
|
||||
{{
|
||||
(configuration_luks_auto_method == 'tpm2' and (_tpm2_method | default('systemd-cryptenroll')) == 'systemd-cryptenroll')
|
||||
| ternary(
|
||||
['tpm2-device=' + configuration_luks_tpm2_device]
|
||||
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
||||
if configuration_luks_tpm2_pcrs | length > 0 else []),
|
||||
[]
|
||||
)
|
||||
}}
|
||||
luks_crypttab_keyfile: "{{ configuration_luks_keyfile_path if luks_keyfile_in_use else 'none' }}"
|
||||
luks_crypttab_options: >-
|
||||
{{
|
||||
(['luks'] + luks_option_list + luks_tpm2_option_list)
|
||||
| join(',')
|
||||
}}
|
||||
luks_rd_options: "{{ (luks_option_list + luks_tpm2_option_list) | join(',') }}"
|
||||
luks_kernel_args: >-
|
||||
{{
|
||||
(
|
||||
['rd.luks.name=' + configuration_luks_uuid + '=' + configuration_luks_mapper_name]
|
||||
+ (
|
||||
['rd.luks.options=' + configuration_luks_uuid + '=' + luks_rd_options]
|
||||
if luks_rd_options | length > 0 else []
|
||||
)
|
||||
+ (
|
||||
['rd.luks.key=' + configuration_luks_uuid + '=' + configuration_luks_keyfile_path]
|
||||
if luks_keyfile_in_use else []
|
||||
)
|
||||
) | join(' ')
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
configuration_luks_keyfile_in_use: "{{ luks_keyfile_in_use }}"
|
||||
configuration_luks_option_list: "{{ luks_option_list }}"
|
||||
configuration_luks_tpm2_option_list: "{{ luks_tpm2_option_list }}"
|
||||
configuration_luks_crypttab_keyfile: "{{ luks_crypttab_keyfile }}"
|
||||
configuration_luks_crypttab_options: "{{ luks_crypttab_options }}"
|
||||
configuration_luks_rd_options: "{{ luks_rd_options }}"
|
||||
configuration_luks_kernel_args: "{{ luks_kernel_args }}"
|
||||
|
||||
- name: Remove LUKS keyfile if TPM2 auto-decrypt is active
|
||||
when: configuration_luks_auto_method == 'tpm2'
|
||||
ansible.builtin.file:
|
||||
path: /mnt{{ configuration_luks_keyfile_path }}
|
||||
state: absent
|
||||
|
||||
- name: Configure initramfs for LUKS
|
||||
ansible.builtin.include_tasks: encryption/initramfs.yml
|
||||
|
||||
- name: Configure crypttab
|
||||
ansible.builtin.include_tasks: encryption/crypttab.yml
|
||||
|
||||
- name: Configure dracut for LUKS
|
||||
when: _initramfs_generator | default('') == 'dracut'
|
||||
ansible.builtin.include_tasks: encryption/dracut.yml
|
||||
|
||||
- name: Configure GRUB for LUKS
|
||||
when: _initramfs_generator | default('') != 'dracut' or os_family != 'RedHat'
|
||||
ansible.builtin.include_tasks: encryption/grub.yml
|
||||
@@ -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,66 +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' %}
|
||||
install_items+=" {{ configuration_luks_tpm2_token_lib | default('') }} "
|
||||
{% endif %}
|
||||
mode: "0644"
|
||||
|
||||
# --- Kernel cmdline: write rd.luks.* args for dracut ---
|
||||
- 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"
|
||||
|
||||
# --- BLS entries: RedHat-specific ---
|
||||
- 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,152 +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'
|
||||
ansible.builtin.command: >-
|
||||
{{ chroot_command }} apt install -y clevis clevis-luks clevis-tpm2 clevis-initramfs tpm2-tools
|
||||
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 }}"
|
||||
@@ -1,116 +0,0 @@
|
||||
---
|
||||
- name: Configure LUKS keyfile auto-decrypt
|
||||
block:
|
||||
- name: Ensure cryptsetup key directory exists
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/cryptsetup-keys.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
|
||||
- name: Ensure LUKS keyfile exists
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt{{ configuration_luks_keyfile_path }}
|
||||
content: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.general.random_string',
|
||||
length=(system_cfg.luks.keysize | int),
|
||||
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
)
|
||||
}}
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
force: false
|
||||
register: configuration_luks_keyfile_copy
|
||||
no_log: true
|
||||
|
||||
- name: Ensure keyfile permissions
|
||||
ansible.builtin.file:
|
||||
path: /mnt{{ configuration_luks_keyfile_path }}
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
|
||||
- name: Check whether keyfile already unlocks the LUKS device
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- cryptsetup
|
||||
- luksOpen
|
||||
- --test-passphrase
|
||||
- --key-file
|
||||
- "/mnt{{ configuration_luks_keyfile_path }}"
|
||||
- "{{ configuration_luks_device }}"
|
||||
register: configuration_luks_keyfile_unlock_test
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Add keyfile to LUKS header
|
||||
when: configuration_luks_keyfile_unlock_test.rc != 0
|
||||
community.crypto.luks_device:
|
||||
device: "{{ configuration_luks_device }}"
|
||||
passphrase: "{{ configuration_luks_passphrase }}"
|
||||
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
||||
register: configuration_luks_addkey_result
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Regenerate keyfile and retry adding to LUKS header
|
||||
when:
|
||||
- configuration_luks_keyfile_unlock_test.rc != 0
|
||||
- configuration_luks_keyfile_copy is defined and configuration_luks_keyfile_copy.changed | bool
|
||||
- configuration_luks_addkey_result is failed
|
||||
block:
|
||||
- name: Regenerate LUKS keyfile
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt{{ configuration_luks_keyfile_path }}
|
||||
content: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.general.random_string',
|
||||
length=(system_cfg.luks.keysize | int),
|
||||
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
)
|
||||
}}
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
force: true
|
||||
no_log: true
|
||||
|
||||
- name: Retry adding keyfile to LUKS header
|
||||
community.crypto.luks_device:
|
||||
device: "{{ configuration_luks_device }}"
|
||||
passphrase: "{{ configuration_luks_passphrase }}"
|
||||
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Re-check whether keyfile unlocks the LUKS device
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- cryptsetup
|
||||
- luksOpen
|
||||
- --test-passphrase
|
||||
- --key-file
|
||||
- "/mnt{{ configuration_luks_keyfile_path }}"
|
||||
- "{{ configuration_luks_device }}"
|
||||
register: configuration_luks_keyfile_unlock_test_after
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
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
|
||||
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
|
||||
ansible.builtin.set_fact:
|
||||
configuration_luks_auto_method: manual
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
block:
|
||||
- name: Create temporary passphrase file for TPM2 enrollment
|
||||
ansible.builtin.tempfile:
|
||||
path: /mnt/root
|
||||
prefix: luks-passphrase-
|
||||
state: file
|
||||
register: _tpm2_passphrase_tempfile
|
||||
|
||||
- name: Write passphrase into temporary file
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ _tpm2_passphrase_tempfile.path }}"
|
||||
content: "{{ configuration_luks_passphrase }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
|
||||
- name: Ensure TPM device is accessible in chroot
|
||||
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:
|
||||
_enroll_args: >-
|
||||
{{
|
||||
[
|
||||
'/usr/bin/systemd-cryptenroll',
|
||||
'--tpm2-device=' + configuration_luks_tpm2_device,
|
||||
'--tpm2-with-pin=false',
|
||||
'--wipe-slot=tpm2',
|
||||
'--unlock-key-file=' + (
|
||||
_tpm2_passphrase_tempfile.path | regex_replace('^/mnt', '')
|
||||
)
|
||||
]
|
||||
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
||||
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
||||
+ [configuration_luks_device]
|
||||
}}
|
||||
ansible.builtin.command: "{{ chroot_command }} {{ _enroll_args | join(' ') }}"
|
||||
register: _tpm2_enroll_result
|
||||
changed_when: _tpm2_enroll_result.rc == 0
|
||||
|
||||
rescue:
|
||||
- name: TPM2 enrollment failed
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
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:
|
||||
- name: Remove temporary passphrase file
|
||||
when: _tpm2_passphrase_tempfile.path is defined
|
||||
ansible.builtin.file:
|
||||
path: "{{ _tpm2_passphrase_tempfile.path }}"
|
||||
state: absent
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
- name: Append vim configurations to vimrc
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ '/mnt/etc/vim/vimrc' if os_family == 'Debian' else '/mnt/etc/vimrc' }}"
|
||||
block: |
|
||||
set encoding=utf-8
|
||||
set number
|
||||
set autoindent
|
||||
set smartindent
|
||||
set mouse=a
|
||||
insertafter: EOF
|
||||
marker: "\" {mark} CUSTOM VIM CONFIG"
|
||||
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
|
||||
ansible.builtin.blockinfile:
|
||||
path: /mnt/etc/sysctl.d/90-memory.conf
|
||||
create: true
|
||||
block: |
|
||||
vm.swappiness=10
|
||||
vm.vfs_cache_pressure=50
|
||||
vm.dirty_background_ratio=1
|
||||
vm.dirty_ratio=10
|
||||
vm.page-cluster=10
|
||||
marker: "# {mark} MEMORY TUNING"
|
||||
mode: "0644"
|
||||
|
||||
- name: Create zram config
|
||||
when:
|
||||
- (os != "debian" or (os_version | string) != "11") and os != "rhel"
|
||||
- os not in ["alpine", "void"]
|
||||
- system_cfg.features.swap.enabled | bool
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/systemd/zram-generator.conf
|
||||
content: |
|
||||
[zram0]
|
||||
zram-size = ram / 2
|
||||
compression-algorithm = {{ 'zstd' if system_cfg.features.zstd.enabled | bool else 'lz4' }}
|
||||
swap-priority = 100
|
||||
fs-type = swap
|
||||
mode: "0644"
|
||||
|
||||
- name: Copy Custom Shell config
|
||||
ansible.builtin.copy:
|
||||
src: custom.sh
|
||||
dest: /mnt/etc/profile.d/custom.sh
|
||||
mode: "0644"
|
||||
@@ -1,80 +0,0 @@
|
||||
---
|
||||
- name: Generate fstab content
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- genfstab
|
||||
- -LU
|
||||
- /mnt
|
||||
register: configuration_fstab_result
|
||||
changed_when: false
|
||||
|
||||
- name: Write fstab
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/fstab
|
||||
content: "{{ configuration_fstab_result.stdout }}\n"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Adjust XFS mount options and disable large extent
|
||||
when: os in ["almalinux", "rocky", "rhel"] and system_cfg.filesystem == "xfs"
|
||||
ansible.builtin.replace:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: "(xfs.*?)(attr2)"
|
||||
replace: "\\1allocsize=64m"
|
||||
|
||||
- name: Remove RHEL ISO fstab entry when not using local repo
|
||||
when:
|
||||
- os == "rhel"
|
||||
- system_cfg.features.rhel_repo.source != "iso"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: "^.*\\/dvd.*$"
|
||||
state: absent
|
||||
|
||||
- name: Replace ISO UUID entry with /dev/sr0 in fstab
|
||||
when:
|
||||
- os == "rhel"
|
||||
- system_cfg.features.rhel_repo.source == "iso"
|
||||
vars:
|
||||
configuration_fstab_dvd_line: >-
|
||||
{{
|
||||
'/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0'
|
||||
if hypervisor_type == 'vmware'
|
||||
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0'
|
||||
}}
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: "^.*\\/dvd.*$"
|
||||
line: "{{ configuration_fstab_dvd_line }}"
|
||||
state: present
|
||||
|
||||
- name: Write image from RHEL ISO to the target machine
|
||||
when:
|
||||
- os == "rhel"
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.features.rhel_repo.source == "iso"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- dd
|
||||
- if=/dev/sr1
|
||||
- of=/mnt/usr/local/install/redhat/rhel.iso
|
||||
- bs=4M
|
||||
creates: /mnt/usr/local/install/redhat/rhel.iso
|
||||
register: configuration_rhel_iso_result
|
||||
changed_when: configuration_rhel_iso_result.rc == 0
|
||||
|
||||
- name: Ensure TempFS is configured in fstab
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: "{{ fstab_entry.regexp }}"
|
||||
line: "{{ fstab_entry.line }}"
|
||||
insertafter: EOF
|
||||
loop:
|
||||
- { regexp: "^# TempFS$", line: "# TempFS" }
|
||||
- { 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+/dev/shm\\s+", line: "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" }
|
||||
loop_control:
|
||||
loop_var: fstab_entry
|
||||
label: "{{ fstab_entry.regexp }}"
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
- name: Configure grub defaults
|
||||
when: os_family != 'RedHat'
|
||||
ansible.builtin.lineinfile:
|
||||
dest: /mnt/etc/default/grub
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
|
||||
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
|
||||
- regexp: ^GRUB_TIMEOUT=
|
||||
line: GRUB_TIMEOUT=1
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
- name: Ensure grub defaults file exists for RHEL-based systems
|
||||
when: os_family == 'RedHat'
|
||||
block:
|
||||
- name: Build RHEL kernel command line defaults
|
||||
vars:
|
||||
grub_root_uuid: >-
|
||||
{{
|
||||
(
|
||||
partitioning_main_uuid.stdout
|
||||
if system_cfg.filesystem == 'btrfs'
|
||||
else (partitioning_uuid_root | default([]) | first | default(''))
|
||||
)
|
||||
| default('')
|
||||
| trim
|
||||
}}
|
||||
grub_lvm_args: >-
|
||||
{{
|
||||
(
|
||||
['rd.lvm.lv=sys/root']
|
||||
+ (
|
||||
['rd.lvm.lv=sys/swap', 'resume=/dev/mapper/sys-swap']
|
||||
if system_cfg.features.swap.enabled | bool
|
||||
else []
|
||||
)
|
||||
)
|
||||
if system_cfg.filesystem != 'btrfs'
|
||||
else []
|
||||
}}
|
||||
grub_root_flags: >-
|
||||
{{ ['rootflags=subvol=@'] if system_cfg.filesystem == 'btrfs' else [] }}
|
||||
grub_cmdline_linux_base: >-
|
||||
{{
|
||||
(['crashkernel=auto'] + grub_lvm_args)
|
||||
| join(' ')
|
||||
}}
|
||||
grub_kernel_cmdline_base: >-
|
||||
{{
|
||||
(
|
||||
(['root=UUID=' + grub_root_uuid]
|
||||
if grub_root_uuid | length > 0 else [])
|
||||
+ ['ro', 'crashkernel=auto']
|
||||
+ grub_lvm_args
|
||||
+ grub_root_flags
|
||||
)
|
||||
| join(' ')
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
configuration_grub_cmdline_linux_base: "{{ grub_cmdline_linux_base }}"
|
||||
configuration_kernel_cmdline_base: "{{ grub_kernel_cmdline_base }}"
|
||||
|
||||
- name: Check if grub defaults file exists
|
||||
ansible.builtin.stat:
|
||||
path: /mnt/etc/default/grub
|
||||
register: configuration_grub_defaults_stat
|
||||
changed_when: false
|
||||
|
||||
- name: Create default grub configuration
|
||||
when: not configuration_grub_defaults_stat.stat.exists
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/default/grub
|
||||
mode: "0644"
|
||||
content: |
|
||||
GRUB_TIMEOUT=1
|
||||
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
|
||||
GRUB_DEFAULT=saved
|
||||
GRUB_DISABLE_SUBMENU=true
|
||||
GRUB_TERMINAL_OUTPUT="console"
|
||||
GRUB_CMDLINE_LINUX="{{ configuration_grub_cmdline_linux_base }}"
|
||||
GRUB_DISABLE_RECOVERY="true"
|
||||
GRUB_ENABLE_BLSCFG=true
|
||||
|
||||
- name: Ensure kernel cmdline directory exists
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/kernel
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Write kernel cmdline defaults
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/kernel/cmdline
|
||||
mode: "0644"
|
||||
content: "{{ configuration_kernel_cmdline_base }}\n"
|
||||
|
||||
- name: Update BLS entries with kernel cmdline defaults
|
||||
vars:
|
||||
_bls_cmdline: "{{ configuration_kernel_cmdline_base }}"
|
||||
ansible.builtin.include_tasks: _bls_update.yml
|
||||
|
||||
- name: Enable GRUB cryptodisk for encrypted /boot
|
||||
when: partitioning_grub_enable_cryptodisk | bool
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/default/grub
|
||||
regexp: "^GRUB_ENABLE_CRYPTODISK="
|
||||
line: GRUB_ENABLE_CRYPTODISK=y
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
- name: Reload systemd in installer environment
|
||||
when: ansible_service_mgr == 'systemd'
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Set local timezone
|
||||
ansible.builtin.file:
|
||||
src: /usr/share/zoneinfo/{{ system_cfg.timezone }}
|
||||
dest: /mnt/etc/localtime
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Setup locales
|
||||
block:
|
||||
- name: Configure locale.gen
|
||||
when: _configuration_platform.locale_gen
|
||||
ansible.builtin.lineinfile:
|
||||
dest: /mnt/etc/locale.gen
|
||||
regexp: "{{ item.regex }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- { regex: "{{ system_cfg.locale }} UTF-8", line: "{{ system_cfg.locale }} UTF-8" }
|
||||
loop_control:
|
||||
label: "{{ item.line }}"
|
||||
|
||||
- name: Generate locales
|
||||
when: _configuration_platform.locale_gen
|
||||
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/locale-gen"
|
||||
register: configuration_locale_result
|
||||
changed_when: configuration_locale_result.rc == 0
|
||||
|
||||
- name: Compute hostname variables
|
||||
ansible.builtin.set_fact:
|
||||
configuration_dns_domain: >-
|
||||
{{ (system_cfg.network.dns.search | default([]) | first | default('')) | string }}
|
||||
configuration_hostname_fqdn: >-
|
||||
{{
|
||||
hostname
|
||||
if '.' in hostname
|
||||
else (
|
||||
hostname + '.' + (system_cfg.network.dns.search | default([]) | first | default('') | string)
|
||||
if (system_cfg.network.dns.search | default([]) | first | default('') | string) | length > 0
|
||||
else hostname
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Set hostname
|
||||
ansible.builtin.copy:
|
||||
content: "{{ configuration_hostname_fqdn.split('.')[0] }}"
|
||||
dest: /mnt/etc/hostname
|
||||
mode: "0644"
|
||||
|
||||
- name: Add host entry to /etc/hosts
|
||||
vars:
|
||||
configuration_hostname_short: "{{ hostname.split('.')[0] }}"
|
||||
configuration_hostname_entries: >-
|
||||
{{ [configuration_hostname_fqdn, configuration_hostname_short] | unique | join(' ') }}
|
||||
configuration_hosts_ip: >-
|
||||
{{
|
||||
system_cfg.network.ip
|
||||
if system_cfg.network.ip is defined and (system_cfg.network.ip | string | length) > 0
|
||||
else inventory_hostname
|
||||
}}
|
||||
configuration_hosts_line: >-
|
||||
{{ configuration_hosts_ip }} {{ configuration_hostname_entries }}
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/hosts
|
||||
line: "{{ configuration_hosts_line }}"
|
||||
state: present
|
||||
|
||||
- name: Create vconsole.conf
|
||||
ansible.builtin.copy:
|
||||
content: "KEYMAP={{ system_cfg.keymap }}"
|
||||
dest: /mnt/etc/vconsole.conf
|
||||
mode: "0644"
|
||||
|
||||
- name: Create locale.conf
|
||||
ansible.builtin.copy:
|
||||
content: "LANG={{ system_cfg.locale }}"
|
||||
dest: /mnt/etc/locale.conf
|
||||
mode: "0644"
|
||||
@@ -1,30 +1,301 @@
|
||||
---
|
||||
- name: Resolve platform configuration
|
||||
ansible.builtin.import_tasks: _resolve_platform.yml
|
||||
- name: Configuration
|
||||
block:
|
||||
- name: Generate fstab
|
||||
ansible.builtin.shell: genfstab -LU /mnt > /mnt/etc/fstab
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Include configuration tasks
|
||||
when: configuration_task.when | default(true)
|
||||
ansible.builtin.include_tasks: "{{ configuration_task.file }}"
|
||||
loop:
|
||||
- file: repositories.yml
|
||||
when: "{{ os_family == 'Debian' }}"
|
||||
- file: banner.yml
|
||||
- file: fstab.yml
|
||||
- file: locales.yml
|
||||
- file: ssh.yml
|
||||
- file: services.yml
|
||||
- file: grub.yml
|
||||
- file: encryption.yml
|
||||
when: "{{ system_cfg.luks.enabled | bool }}"
|
||||
- file: bootloader.yml
|
||||
- file: secure_boot.yml
|
||||
when: "{{ system_cfg.features.secure_boot.enabled | bool }}"
|
||||
- file: extras.yml
|
||||
- file: network.yml
|
||||
- file: users.yml
|
||||
- file: sudo.yml
|
||||
- file: selinux.yml
|
||||
when: "{{ os_family == 'RedHat' }}"
|
||||
loop_control:
|
||||
loop_var: configuration_task
|
||||
label: "{{ configuration_task.file }}"
|
||||
- name: Remove depricated attr2 and disable large extent
|
||||
when: os in ["almalinux", "rhel8", "rhel9", "rocky"] and filesystem == "xfs"
|
||||
ansible.builtin.replace:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: '(xfs.*?)(attr2)'
|
||||
replace: '\1allocsize=64m'
|
||||
|
||||
- name: Replace ISO UUID entry with /dev/sr0 in fstab
|
||||
when: os in ["rhel8", "rhel9"]
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
regexp: '^.*\/dvd.*$'
|
||||
line: "{{ '/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0' if hypervisor == 'vmware'
|
||||
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0' }}"
|
||||
state: present
|
||||
backrefs: true
|
||||
|
||||
- name: Write image from RHEL ISO to the target machine
|
||||
when: os in ["rhel8", "rhel9"] and hypervisor == 'vmware'
|
||||
ansible.builtin.command: dd if=/dev/sr1 of=/mnt/usr/local/install/redhat/rhel.iso bs=4M
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Append TempFS to fstab
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/fstab
|
||||
line: "{{ item }}"
|
||||
insertafter: EOF
|
||||
with_items:
|
||||
- ""
|
||||
- "# TempFS"
|
||||
- tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0
|
||||
- tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0
|
||||
- tmpfs /dev/shm tmpfs defaults,noexec 0 0
|
||||
|
||||
- name: Set local timezone
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
with_items:
|
||||
- systemctl daemon-reload
|
||||
- arch-chroot /mnt ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
|
||||
|
||||
- name: Setup locales
|
||||
block:
|
||||
- name: Configure locale.gen
|
||||
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
|
||||
ansible.builtin.lineinfile:
|
||||
dest: /mnt/etc/locale.gen
|
||||
regexp: "{{ item.regex }}"
|
||||
line: "{{ item.line }}"
|
||||
loop:
|
||||
- { regex: en_US\.UTF-8 UTF-8, line: en_US.UTF-8 UTF-8 }
|
||||
|
||||
- name: Generate locales
|
||||
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
|
||||
ansible.builtin.command: arch-chroot /mnt /usr/sbin/locale-gen
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Set hostname
|
||||
ansible.builtin.copy:
|
||||
content: "{{ hostname }}"
|
||||
dest: /mnt/etc/hostname
|
||||
mode: '0644'
|
||||
|
||||
- name: Add host entry to /etc/hosts
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/hosts
|
||||
line: "{{ ansible_host }} {{ hostname }}"
|
||||
state: present
|
||||
|
||||
- name: Create vconsole.conf
|
||||
ansible.builtin.copy:
|
||||
content: KEYMAP=us
|
||||
dest: /mnt/etc/vconsole.conf
|
||||
mode: '0644'
|
||||
|
||||
- name: Create locale.conf
|
||||
ansible.builtin.copy:
|
||||
content: LANG=en_US.UTF-8
|
||||
dest: /mnt/etc/locale.conf
|
||||
mode: '0644'
|
||||
|
||||
- name: SSH permit Password
|
||||
ansible.builtin.replace:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
regexp: "#PasswordAuthentication yes"
|
||||
replace: PasswordAuthentication yes
|
||||
|
||||
- name: SSH permit root login
|
||||
ansible.builtin.replace:
|
||||
path: /mnt/etc/ssh/sshd_config
|
||||
regexp: "^#?PermitRootLogin.*"
|
||||
replace: "PermitRootLogin yes"
|
||||
|
||||
- name: Enable Systemd Services
|
||||
ansible.builtin.command: >
|
||||
arch-chroot /mnt systemctl enable NetworkManager
|
||||
{{
|
||||
' ssh' if os | lower in ['ubuntu', 'ubuntu-lts'] else
|
||||
(' sshd' if os | lower not in ['debian11', 'debian12'] else '')
|
||||
}}
|
||||
{{
|
||||
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
|
||||
if os | lower == 'archlinux' else ''
|
||||
}}
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Configure grub
|
||||
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
|
||||
block:
|
||||
- name: Add commandline information to grub config
|
||||
ansible.builtin.lineinfile:
|
||||
dest: /mnt/etc/default/grub
|
||||
regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
|
||||
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
|
||||
|
||||
- name: Change Grub time
|
||||
ansible.builtin.lineinfile:
|
||||
dest: /mnt/etc/default/grub
|
||||
regexp: ^GRUB_TIMEOUT=
|
||||
line: GRUB_TIMEOUT=1
|
||||
|
||||
- name: Configure Bootloader
|
||||
block:
|
||||
- name: Install Bootloader
|
||||
ansible.builtin.command: arch-chroot /mnt
|
||||
{% if os | lower not in ["archlinux", "debian11", "debian12", "ubuntu", "ubuntu-lts"] %} /usr/sbin/efibootmgr
|
||||
-c -L '{{ os }}' -d "{{ install_drive }}" -p 1
|
||||
-l '\efi\EFI\{% if os | lower in ["rhel8", "rhel9"] %}redhat{% else %}{{ os | lower }}{% endif %}\shimx64.efi'
|
||||
{% else %}/usr/sbin/grub-install --target=x86_64-efi --efi-directory={{ "/boot/efi" if os | lower in ["ubuntu", "ubuntu-lts"] else "/boot" }}
|
||||
--bootloader-id={{ "ubuntu" if os | lower in ["ubuntu", "ubuntu-lts"] else os }}
|
||||
{% endif %}
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Generate grub config
|
||||
ansible.builtin.command: arch-chroot /mnt
|
||||
{% if os | lower not in ["archlinux", "debian11", "debian12", "ubuntu", "ubuntu-lts"] %}
|
||||
/usr/sbin/grub2-mkconfig -o /boot/efi/EFI/{% if os | lower in ["rhel8", "rhel9"] %}redhat{% else %}{{ os | lower }}{% endif %}/grub.cfg
|
||||
{% else %}
|
||||
/usr/sbin/grub-mkconfig -o {{ "/boot/efi/EFI/ubuntu/grub.cfg" if os | lower in ["ubuntu", "ubuntu-lts"] else "/boot/grub/grub.cfg" }}
|
||||
{% endif %}
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Ensure lvm2 for non btrfs filesystems
|
||||
when: os | lower == "archlinux" and filesystem != "btrfs"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/mkinitcpio.conf
|
||||
regexp: '^(HOOKS=.*block)(?!.*lvm2)(.*)'
|
||||
line: '\1 lvm2\2'
|
||||
backrefs: true
|
||||
|
||||
- name: Regenerate initramfs
|
||||
when: os | lower not in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
|
||||
ansible.builtin.command: arch-chroot /mnt
|
||||
{% if os | lower == "archlinux" %} /usr/sbin/mkinitcpio -P
|
||||
{% elif os | lower not in ["debian11", "debian12", "ubuntu", "ubuntu-lts", "archlinux"] %} /usr/bin/dracut --regenerate-all --force
|
||||
{% else %} echo "Skipping initramfs regeneration"
|
||||
{% endif %}
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Extra Configuration
|
||||
block:
|
||||
- name: Append vim configurations to vimrc
|
||||
failed_when: false
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ '/mnt/etc/vim/vimrc' if os | lower in ['debian11', 'debian12', 'ubuntu', 'ubuntu-lts']
|
||||
else '/mnt/etc/vimrc' }}"
|
||||
block: |
|
||||
set encoding=utf-8
|
||||
set number
|
||||
set autoindent
|
||||
set smartindent
|
||||
set mouse=a
|
||||
insertafter: EOF
|
||||
marker: ""
|
||||
|
||||
- name: Add memory tuning parameters
|
||||
ansible.builtin.blockinfile:
|
||||
path: /mnt/etc/sysctl.d/90-memory.conf
|
||||
create: true
|
||||
block: |
|
||||
vm.swappiness=10
|
||||
vm.vfs_cache_pressure=50
|
||||
vm.dirty_background_ratio=1
|
||||
vm.dirty_ratio=10
|
||||
vm.page-cluster=10
|
||||
marker: ""
|
||||
mode: '0644'
|
||||
|
||||
- name: Create zram config
|
||||
when: os not in ['debian11', 'rhel8']
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/systemd/zram-generator.conf
|
||||
content: |
|
||||
[zram0]
|
||||
zram-size = ram / 2
|
||||
compression-algorithm = zstd
|
||||
swap-priority = 100
|
||||
fs-type = swap
|
||||
mode: '0644'
|
||||
|
||||
- name: Copy FirstRun Script
|
||||
when: os | lower != "archlinux"
|
||||
ansible.builtin.template:
|
||||
src: firstrun.sh.j2
|
||||
dest: /mnt/root/firstrun.sh
|
||||
mode: "0755"
|
||||
|
||||
- name: Copy Custom Shell config
|
||||
ansible.builtin.template:
|
||||
src: custom.sh.j2
|
||||
dest: /mnt/etc/profile.d/custom.sh
|
||||
mode: '0644'
|
||||
|
||||
- name: Setup Network
|
||||
block:
|
||||
- name: Generate UUID for Network Profile
|
||||
ansible.builtin.command: uuidgen
|
||||
changed_when: net_uuid.rc == 0
|
||||
register: net_uuid
|
||||
|
||||
- name: Retrieve Network Interface Name
|
||||
ansible.builtin.shell: set -o pipefail && ip r | awk 'NR==1 {print $5}'
|
||||
changed_when: net_inf.rc == 0
|
||||
register: net_inf
|
||||
|
||||
- name: Register MAC Address of the Network Interface
|
||||
ansible.builtin.shell: set -o pipefail && ip link show "{{ net_inf.stdout }}" | awk '/link\/ether/ {print $2}' | tr '[:lower:]' '[:upper:]'
|
||||
register: net_mac
|
||||
changed_when: net_mac.rc == 0
|
||||
|
||||
- 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: Setup user account
|
||||
block:
|
||||
- name: Create user account
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
with_items:
|
||||
- arch-chroot /mnt /usr/sbin/useradd --create-home --user-group --groups
|
||||
{{ "sudo" if os | lower in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "wheel" }}
|
||||
{{ user_name }} --password {{ user_password | password_hash('sha512') }} --shell /bin/bash
|
||||
- arch-chroot /mnt /usr/sbin/usermod --password '{{ root_password | password_hash('sha512') }}' root --shell /bin/bash
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Add SSH public key to authorized_keys
|
||||
when: user_public_key is defined
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/home/{{ user_name }}/.ssh/authorized_keys
|
||||
line: "{{ user_public_key }}"
|
||||
owner: 1000
|
||||
group: 1000
|
||||
mode: "0600"
|
||||
create: true
|
||||
|
||||
- name: Give sudo access to wheel group
|
||||
ansible.builtin.copy:
|
||||
content: "{{ '%sudo ALL=(ALL) ALL' if os | lower in ['debian11', 'debian12', 'ubuntu', 'ubuntu-lts'] else '%wheel ALL=(ALL) ALL' }}"
|
||||
dest: /mnt/etc/sudoers.d/01-wheel
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo --check --file=%s
|
||||
|
||||
- name: Fix SELinux
|
||||
block:
|
||||
- name: Relabel the filesystem
|
||||
when: os | lower in ['almalinux', 'rhel8', 'rhel9', 'rocky']
|
||||
ansible.builtin.command: "arch-chroot /mnt /sbin/fixfiles onboot"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Disable SELinux
|
||||
when: os | lower == "fedora"
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/selinux/config
|
||||
regexp: ^SELINUX=
|
||||
line: SELINUX=permissive
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
- name: Read network interfaces
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- ip
|
||||
- -o
|
||||
- link
|
||||
- show
|
||||
register: configuration_ip_link
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Detect available network interface names
|
||||
vars:
|
||||
configuration_detected_interfaces: >-
|
||||
{{
|
||||
configuration_ip_link.stdout
|
||||
| default('')
|
||||
| regex_findall('^[0-9]+: ([^:]+):', multiline=True)
|
||||
| reject('equalto', 'lo')
|
||||
| list
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
configuration_detected_interfaces: "{{ configuration_detected_interfaces }}"
|
||||
|
||||
- name: Validate at least one network interface detected
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- configuration_detected_interfaces | length > 0
|
||||
fail_msg: Failed to detect any network interfaces.
|
||||
|
||||
- name: Set DNS configuration facts
|
||||
ansible.builtin.set_fact:
|
||||
configuration_dns_list: "{{ system_cfg.network.dns.servers }}"
|
||||
configuration_dns_search: "{{ system_cfg.network.dns.search }}"
|
||||
|
||||
- name: Configure networking
|
||||
ansible.builtin.include_tasks: "{{ configuration_network_task_map[os] | default('network_nm.yml') }}"
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
- name: Write Alpine network interfaces
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/network/interfaces
|
||||
mode: "0644"
|
||||
content: |
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set inv_name = iface.name | default('') | string %}
|
||||
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
|
||||
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
|
||||
auto {{ iface_name }}
|
||||
iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
|
||||
{% if has_static %}
|
||||
address {{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
gateway {{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
- name: Set Alpine DNS resolvers
|
||||
when: configuration_dns_list | length > 0 or configuration_dns_search | length > 0
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/resolv.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% if configuration_dns_search | length > 0 %}
|
||||
search {{ configuration_dns_search | join(' ') }}
|
||||
{% endif %}
|
||||
{% for resolver in configuration_dns_list %}
|
||||
nameserver {{ resolver }}
|
||||
{% endfor %}
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
- name: Copy NetworkManager keyfile per interface
|
||||
vars:
|
||||
configuration_iface: "{{ item }}"
|
||||
configuration_iface_name: "{{ item.name | default(configuration_detected_interfaces[idx] | default('')) }}"
|
||||
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,26 +0,0 @@
|
||||
---
|
||||
- name: Write dhcpcd configuration
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/dhcpcd.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
{% for iface in system_cfg.network.interfaces %}
|
||||
{% set inv_name = iface.name | default('') | string %}
|
||||
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
|
||||
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
|
||||
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
|
||||
{% if has_static %}
|
||||
interface {{ iface_name }}
|
||||
static ip_address={{ iface.ip }}/{{ iface.prefix }}
|
||||
{% if iface.gateway | default('') | string | length %}
|
||||
static routers={{ iface.gateway }}
|
||||
{% endif %}
|
||||
{% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
|
||||
static domain_name_servers={{ configuration_dns_list | join(' ') }}
|
||||
{% endif %}
|
||||
{% if loop.index0 == 0 and configuration_dns_search | length > 0 %}
|
||||
static domain_search={{ configuration_dns_search | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
- name: Write final sources.list
|
||||
vars:
|
||||
_debian_release_map:
|
||||
"10": buster
|
||||
"11": bullseye
|
||||
"12": bookworm
|
||||
"13": trixie
|
||||
unstable: sid
|
||||
_ubuntu_release_map:
|
||||
ubuntu: questing
|
||||
ubuntu-lts: noble
|
||||
ansible.builtin.template:
|
||||
src: "{{ os | replace('-lts', '') }}.sources.list.j2"
|
||||
dest: /mnt/etc/apt/sources.list
|
||||
mode: "0644"
|
||||
|
||||
- name: Ensure apt performance configuration persists
|
||||
ansible.builtin.copy:
|
||||
dest: /mnt/etc/apt/apt.conf.d/99performance
|
||||
content: |
|
||||
Acquire::Retries "3";
|
||||
Acquire::http::Pipeline-Depth "10";
|
||||
APT::Install-Recommends "false";
|
||||
mode: "0644"
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
- 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,21 +0,0 @@
|
||||
---
|
||||
- name: Fix SELinux
|
||||
when: os_family == 'RedHat'
|
||||
block:
|
||||
- name: Fix SELinux by pre-labeling the filesystem before first boot
|
||||
when: os in ['almalinux', 'rocky', 'rhel'] and system_cfg.features.selinux.enabled | bool
|
||||
ansible.builtin.command: >
|
||||
{{ chroot_command }} /sbin/setfiles -v -F
|
||||
-e /dev -e /proc -e /sys -e /run
|
||||
/etc/selinux/targeted/contexts/files/file_contexts /
|
||||
register: configuration_setfiles_result
|
||||
changed_when: configuration_setfiles_result.rc == 0
|
||||
|
||||
# Fedora: setfiles segfaults during bootstrap chroot relabeling, so SELinux
|
||||
# is left permissive and expected to relabel on first boot.
|
||||
- name: Disable SELinux
|
||||
when: os == "fedora" or not system_cfg.features.selinux.enabled | bool
|
||||
ansible.builtin.lineinfile:
|
||||
path: /mnt/etc/selinux/config
|
||||
regexp: ^SELINUX=
|
||||
line: SELINUX=permissive
|
||||
@@ -1,105 +0,0 @@
|
||||
---
|
||||
- name: Enable systemd services
|
||||
when: _configuration_platform.init_system == 'systemd'
|
||||
vars:
|
||||
_desktop_dm: >-
|
||||
{{
|
||||
system_cfg.features.desktop.display_manager
|
||||
if (system_cfg.features.desktop.display_manager | length > 0)
|
||||
else (configuration_desktop_dm_map[system_cfg.features.desktop.environment] | default(''))
|
||||
}}
|
||||
configuration_systemd_services: >-
|
||||
{{
|
||||
['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 [])
|
||||
+ ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else [])
|
||||
+ (['logrotate', 'systemd-timesyncd'] if os == 'archlinux' else [])
|
||||
+ ([_desktop_dm] if system_cfg.features.desktop.enabled | bool and _desktop_dm | length > 0 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
|
||||
|
||||
- name: Activate UFW firewall
|
||||
when:
|
||||
- system_cfg.features.firewall.backend == 'ufw'
|
||||
- system_cfg.features.firewall.enabled | bool
|
||||
ansible.builtin.command: "{{ chroot_command }} ufw --force enable"
|
||||
register: _ufw_enable_result
|
||||
changed_when: _ufw_enable_result.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Set default systemd target to graphical
|
||||
when:
|
||||
- _configuration_platform.init_system == 'systemd'
|
||||
- system_cfg.features.desktop.enabled | bool
|
||||
ansible.builtin.command: "{{ chroot_command }} systemctl set-default graphical.target"
|
||||
register: _desktop_target_result
|
||||
changed_when: _desktop_target_result.rc == 0
|
||||
|
||||
- name: Enable OpenRC services
|
||||
when: _configuration_platform.init_system == 'openrc'
|
||||
vars:
|
||||
configuration_openrc_services: >-
|
||||
{{
|
||||
['networking']
|
||||
+ (['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 OpenRC runlevel directory exists
|
||||
ansible.builtin.file:
|
||||
path: /mnt/etc/runlevels/default
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Check OpenRC init scripts
|
||||
ansible.builtin.stat:
|
||||
path: "/mnt/etc/init.d/{{ item }}"
|
||||
loop: "{{ configuration_openrc_services }}"
|
||||
register: configuration_openrc_service_stats
|
||||
|
||||
- name: Enable OpenRC services
|
||||
ansible.builtin.file:
|
||||
src: "/mnt/etc/init.d/{{ item.item }}"
|
||||
dest: "/mnt/etc/runlevels/default/{{ item.item }}"
|
||||
state: link
|
||||
loop: "{{ configuration_openrc_service_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.stat.exists
|
||||
|
||||
- name: Enable runit services
|
||||
when: _configuration_platform.init_system == 'runit'
|
||||
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
|
||||
mode: "0755"
|
||||
|
||||
- name: Check runit service definitions
|
||||
ansible.builtin.stat:
|
||||
path: "/mnt/etc/sv/{{ item }}"
|
||||
loop: "{{ configuration_runit_services }}"
|
||||
register: configuration_runit_service_stats
|
||||
|
||||
- name: Enable runit services
|
||||
ansible.builtin.file:
|
||||
src: "/mnt/etc/sv/{{ item.item }}"
|
||||
dest: "/mnt/var/service/{{ item.item }}"
|
||||
state: link
|
||||
loop: "{{ configuration_runit_service_stats.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: item.stat.exists
|
||||
@@ -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,29 +0,0 @@
|
||||
---
|
||||
- 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
|
||||
ansible.builtin.copy:
|
||||
content: "{{ _configuration_platform.sudo_group }} ALL=(ALL) ALL\n"
|
||||
dest: /mnt/etc/sudoers.d/01-wheel
|
||||
mode: "0440"
|
||||
validate: /usr/sbin/visudo --check --file=%s
|
||||
|
||||
- name: Deploy per-user sudoers rules
|
||||
when: item.value.sudo is defined and (item.value.sudo | string | length > 0)
|
||||
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,68 +0,0 @@
|
||||
---
|
||||
- name: Set root password
|
||||
when: (system_cfg.root.password | default('') | string | length) > 0
|
||||
ansible.builtin.shell: >-
|
||||
set -o pipefail &&
|
||||
echo 'root:{{ 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:
|
||||
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
||||
configuration_useradd_cmd: >-
|
||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||
--uid {{ 1000 + _idx }}
|
||||
--groups {{ configuration_user_group }} {{ item.key }}
|
||||
{{ ('--password ' ~ (item.value.password | password_hash('sha512'))) if (item.value.password | default('') | string | length > 0) else '' }}
|
||||
--shell {{ item.value.shell | default('/bin/bash') }}
|
||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||
loop: "{{ system_cfg.users | dict2items }}"
|
||||
loop_control:
|
||||
index_var: _idx
|
||||
label: "{{ item.key }}"
|
||||
register: configuration_user_result
|
||||
changed_when: configuration_user_result.rc == 0
|
||||
no_log: true
|
||||
|
||||
- name: Ensure .ssh directory exists
|
||||
when: (item.value['keys'] | default([]) | length) > 0
|
||||
ansible.builtin.file:
|
||||
path: "/mnt/home/{{ item.key }}/.ssh"
|
||||
state: directory
|
||||
owner: "{{ 1000 + _idx }}"
|
||||
group: "{{ 1000 + _idx }}"
|
||||
mode: "0700"
|
||||
loop: "{{ system_cfg.users | dict2items }}"
|
||||
loop_control:
|
||||
index_var: _idx
|
||||
label: "{{ item.key }}"
|
||||
|
||||
- name: Deploy SSH authorized_keys
|
||||
when: (item.value['keys'] | default([]) | length) > 0
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.value['keys'] | join('\n') }}\n"
|
||||
dest: "/mnt/home/{{ item.key }}/.ssh/authorized_keys"
|
||||
owner: "{{ 1000 + _idx }}"
|
||||
group: "{{ 1000 + _idx }}"
|
||||
mode: "0600"
|
||||
loop: "{{ system_cfg.users | dict2items }}"
|
||||
loop_control:
|
||||
index_var: _idx
|
||||
label: "{{ item.key }}"
|
||||
@@ -9,7 +9,4 @@ PROMPT_COMMAND="history -a;$PROMPT_COMMAND"
|
||||
|
||||
# History Size
|
||||
HISTFILESIZE=
|
||||
HISTSIZE=
|
||||
|
||||
# Enable vi mode
|
||||
set -o vi
|
||||
HISTSIZE=
|
||||
@@ -1,15 +0,0 @@
|
||||
# Managed by Ansible.
|
||||
{% set release = _debian_release_map[os_version | string] | default('trixie') %}
|
||||
{% set mirror = system_cfg.mirror %}
|
||||
{% set components = 'main contrib non-free' ~ (' non-free-firmware' if (os_version | string) not in ['10', '11'] else '') %}
|
||||
|
||||
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 %}
|
||||
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,29 +1,16 @@
|
||||
[connection]
|
||||
id=LAN-{{ idx }}
|
||||
uuid={{ configuration_net_uuid }}
|
||||
id=LAN
|
||||
uuid={{ net_uuid.stdout }}
|
||||
type=ethernet
|
||||
autoconnect-priority=10
|
||||
{% if configuration_iface_name | length > 0 %}
|
||||
interface-name={{ configuration_iface_name }}
|
||||
{% endif %}
|
||||
interface-name={{ net_inf.stdout }}
|
||||
|
||||
[ethernet]
|
||||
mac-address={{ net_mac.stdout }}
|
||||
|
||||
[ipv4]
|
||||
{% set iface = configuration_iface %}
|
||||
{% set dns_list = configuration_dns_list %}
|
||||
{% set search_list = configuration_dns_search %}
|
||||
{% if iface.ip | default('') | string | length %}
|
||||
address1={{ iface.ip }}/{{ iface.prefix }}{{ (',' ~ iface.gateway) if (iface.gateway | default('') | string | length) else '' }}
|
||||
address={{ vm_ip }},{{ vm_gw }}
|
||||
dns={{ vm_dns }}
|
||||
method=manual
|
||||
{% else %}
|
||||
method=auto
|
||||
{% endif %}
|
||||
{% if idx | int == 0 and dns_list %}
|
||||
dns={{ dns_list | join(';') }};
|
||||
ignore-auto-dns=true
|
||||
{% endif %}
|
||||
{% if idx | int == 0 and search_list %}
|
||||
dns-search={{ search_list | join(';') }};
|
||||
{% endif %}
|
||||
|
||||
[ipv6]
|
||||
addr-gen-mode=stable-privacy
|
||||
|
||||
11
roles/configuration/templates/sudo_lecture.txt.j2
Normal file
11
roles/configuration/templates/sudo_lecture.txt.j2
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
\^V//
|
||||
|. .| I AM (G)ROOT!
|
||||
- \ - / _
|
||||
\_| |_/
|
||||
\ \
|
||||
__/_/__
|
||||
|_______| With great power comes great responsibility.
|
||||
\ / Use sudo wisely.
|
||||
\___/
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Managed by Ansible.
|
||||
{% set release = _ubuntu_release_map[os] | default('noble') %}
|
||||
{% set mirror = system_cfg.mirror %}
|
||||
{% 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,79 +0,0 @@
|
||||
---
|
||||
# Platform-specific configuration values keyed by os_family.
|
||||
# Consumed as _configuration_platform in tasks via:
|
||||
# configuration_platform_config[os_family]
|
||||
configuration_platform_config:
|
||||
RedHat:
|
||||
user_group: wheel
|
||||
sudo_group: "%wheel"
|
||||
ssh_service: sshd
|
||||
efi_loader: shimx64.efi
|
||||
grub_install: false
|
||||
initramfs_cmd: "/usr/bin/dracut --regenerate-all --force"
|
||||
grub_mkconfig_prefix: grub2-mkconfig
|
||||
locale_gen: false
|
||||
init_system: systemd
|
||||
Debian:
|
||||
user_group: sudo
|
||||
sudo_group: "%sudo"
|
||||
ssh_service: ssh
|
||||
efi_loader: grubx64.efi
|
||||
grub_install: true
|
||||
initramfs_cmd: >-
|
||||
/usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
/usr/sbin/update-initramfs -u -k all
|
||||
grub_mkconfig_prefix: grub-mkconfig
|
||||
locale_gen: true
|
||||
init_system: systemd
|
||||
Archlinux:
|
||||
user_group: wheel
|
||||
sudo_group: "%wheel"
|
||||
ssh_service: sshd
|
||||
efi_loader: grubx64.efi
|
||||
grub_install: true
|
||||
initramfs_cmd: "/usr/sbin/mkinitcpio -P"
|
||||
grub_mkconfig_prefix: grub-mkconfig
|
||||
locale_gen: true
|
||||
init_system: systemd
|
||||
Suse:
|
||||
user_group: wheel
|
||||
sudo_group: "%wheel"
|
||||
ssh_service: sshd
|
||||
efi_loader: grubx64.efi
|
||||
grub_install: true
|
||||
initramfs_cmd: "/usr/bin/dracut --regenerate-all --force"
|
||||
grub_mkconfig_prefix: grub-mkconfig
|
||||
locale_gen: true
|
||||
init_system: systemd
|
||||
Alpine:
|
||||
user_group: wheel
|
||||
sudo_group: "%wheel"
|
||||
ssh_service: sshd
|
||||
efi_loader: grubx64.efi
|
||||
grub_install: true
|
||||
initramfs_cmd: ""
|
||||
grub_mkconfig_prefix: grub-mkconfig
|
||||
locale_gen: false
|
||||
init_system: openrc
|
||||
Void:
|
||||
user_group: wheel
|
||||
sudo_group: "%wheel"
|
||||
ssh_service: sshd
|
||||
efi_loader: grubx64.efi
|
||||
grub_install: true
|
||||
initramfs_cmd: ""
|
||||
grub_mkconfig_prefix: grub-mkconfig
|
||||
locale_gen: false
|
||||
init_system: runit
|
||||
|
||||
# Display manager auto-detection from desktop environment name.
|
||||
configuration_desktop_dm_map:
|
||||
gnome: gdm
|
||||
kde: sddm
|
||||
xfce: lightdm
|
||||
sway: greetd
|
||||
hyprland: ly@tty2
|
||||
cinnamon: lightdm
|
||||
mate: lightdm
|
||||
lxqt: sddm
|
||||
budgie: gdm
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# Connection and timing
|
||||
environment_wait_timeout: 180
|
||||
environment_wait_delay: 5
|
||||
|
||||
# Pacman installer settings
|
||||
environment_parallel_downloads: 20
|
||||
environment_pacman_lock_timeout: 120
|
||||
environment_pacman_retries: 4
|
||||
environment_pacman_retry_delay: 15
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
- name: Select primary Network Interface
|
||||
when: hypervisor_type == "vmware"
|
||||
ansible.builtin.set_fact:
|
||||
environment_interface_name: >-
|
||||
{{
|
||||
(
|
||||
(ansible_facts.interfaces | default(ansible_facts['ansible_interfaces'] | default([])))
|
||||
| reject('equalto', 'lo')
|
||||
| list
|
||||
| first
|
||||
)
|
||||
| default('')
|
||||
}}
|
||||
|
||||
- name: Bring up network interface
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- environment_interface_name | default('') | length > 0
|
||||
ansible.builtin.command: "ip link set {{ environment_interface_name }} up"
|
||||
register: environment_link_result
|
||||
changed_when: environment_link_result.rc == 0
|
||||
|
||||
- name: Set IP-Address
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
|
||||
ansible.builtin.command: >-
|
||||
ip addr replace {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
|
||||
dev {{ environment_interface_name }}
|
||||
register: environment_ip_result
|
||||
changed_when: environment_ip_result.rc == 0
|
||||
|
||||
- name: Set Default Gateway
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length > 0
|
||||
- system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
|
||||
ansible.builtin.command: "ip route replace default via {{ system_cfg.network.gateway }}"
|
||||
register: environment_gateway_result
|
||||
changed_when: environment_gateway_result.rc == 0
|
||||
|
||||
- name: Configure DNS resolvers
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- system_cfg.network.dns.servers | default([]) | length > 0
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/resolv.conf
|
||||
content: |
|
||||
{% for server in system_cfg.network.dns.servers %}
|
||||
nameserver {{ server }}
|
||||
{% endfor %}
|
||||
{% if system_cfg.network.dns.search | default([]) | length > 0 %}
|
||||
search {{ system_cfg.network.dns.search | join(' ') }}
|
||||
{% endif %}
|
||||
mode: "0644"
|
||||
|
||||
- name: Synchronize clock via NTP
|
||||
ansible.builtin.command: timedatectl set-ntp true
|
||||
register: environment_ntp_result
|
||||
changed_when: environment_ntp_result.rc == 0
|
||||
|
||||
- name: Configure SSH for root login
|
||||
when:
|
||||
- hypervisor_type == "vmware"
|
||||
- hypervisor_cfg.ssh | default(false) | bool
|
||||
- system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
|
||||
block:
|
||||
- name: Allow login
|
||||
ansible.builtin.replace:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "{{ item.regexp }}"
|
||||
replace: "{{ item.replace }}"
|
||||
loop:
|
||||
- regexp: "^#?PermitEmptyPasswords.*"
|
||||
replace: "PermitEmptyPasswords yes"
|
||||
- regexp: "^#?PermitRootLogin.*"
|
||||
replace: "PermitRootLogin yes"
|
||||
loop_control:
|
||||
label: "{{ item.replace }}"
|
||||
|
||||
- name: Reload SSH service to apply changes
|
||||
ansible.builtin.service:
|
||||
name: sshd
|
||||
state: reloaded
|
||||
|
||||
- name: Switch to SSH connection
|
||||
ansible.builtin.set_fact:
|
||||
ansible_connection: ssh
|
||||
ansible_host: "{{ system_cfg.network.ip }}"
|
||||
ansible_port: 22
|
||||
ansible_user: root
|
||||
ansible_password: ""
|
||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||
|
||||
- name: Reset connection for SSH switchover
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: Verify SSH connectivity
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 30
|
||||
delay: 2
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
- name: Wait for connection
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: "{{ environment_wait_timeout }}"
|
||||
delay: "{{ environment_wait_delay }}"
|
||||
|
||||
- name: Gather facts
|
||||
ansible.builtin.setup:
|
||||
|
||||
- name: Check for live environment markers
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
loop:
|
||||
- /run/archiso
|
||||
- /run/live
|
||||
- /run/initramfs
|
||||
- /run/initramfs/live
|
||||
register: environment_live_marker_stat
|
||||
changed_when: false
|
||||
|
||||
- name: Determine root filesystem type
|
||||
ansible.builtin.set_fact:
|
||||
environment_root_fstype: >-
|
||||
{{
|
||||
ansible_mounts
|
||||
| selectattr('mount', 'equalto', '/')
|
||||
| map(attribute='fstype')
|
||||
| list
|
||||
| first
|
||||
| default('')
|
||||
| lower
|
||||
}}
|
||||
environment_archiso_present: >-
|
||||
{{
|
||||
(
|
||||
environment_live_marker_stat.results
|
||||
| selectattr('item', 'equalto', '/run/archiso')
|
||||
| selectattr('stat.exists')
|
||||
| list
|
||||
| length
|
||||
) > 0
|
||||
}}
|
||||
|
||||
- name: Identify live environment indicators
|
||||
ansible.builtin.set_fact:
|
||||
environment_is_live_environment: >-
|
||||
{{
|
||||
(
|
||||
environment_live_marker_stat.results
|
||||
| selectattr('stat.exists')
|
||||
| list
|
||||
| length
|
||||
) > 0
|
||||
or environment_root_fstype in ['overlay', 'overlayfs', 'squashfs', 'aufs']
|
||||
or (ansible_hostname | default('') | lower is search('live'))
|
||||
}}
|
||||
|
||||
- name: Abort if target is not a live environment
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- environment_is_live_environment | bool
|
||||
fail_msg: |
|
||||
PRODUCTION SYSTEM DETECTED - ABORTING
|
||||
|
||||
The target system does not appear to be a live installer environment.
|
||||
This playbook must run from a live ISO to avoid wiping production data.
|
||||
|
||||
Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry.
|
||||
quiet: true
|
||||
|
||||
- name: Harden sshd for Ansible automation
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
marker: "# {mark} BOOTSTRAP ANSIBLE SETTINGS"
|
||||
block: |
|
||||
PerSourcePenalties no
|
||||
MaxStartups 50:30:100
|
||||
ClientAliveInterval 30
|
||||
ClientAliveCountMax 10
|
||||
register: _sshd_config_result
|
||||
|
||||
- name: Restart sshd immediately if config was changed
|
||||
when: _sshd_config_result is changed
|
||||
ansible.builtin.service:
|
||||
name: sshd
|
||||
state: restarted
|
||||
|
||||
- name: Abort if the host is not booted from the Arch install media
|
||||
when:
|
||||
- not (custom_iso | bool)
|
||||
- not environment_archiso_present | bool
|
||||
ansible.builtin.fail:
|
||||
msg: This host is not booted from the Arch install media!
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
- name: Speed-up Bootstrap process
|
||||
when: not (custom_iso | bool)
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/pacman.conf
|
||||
regexp: ^#ParallelDownloads =
|
||||
line: "ParallelDownloads = {{ environment_parallel_downloads }}"
|
||||
|
||||
- name: Wait for pacman lock to be released
|
||||
when: not (custom_iso | bool)
|
||||
ansible.builtin.wait_for:
|
||||
path: /var/lib/pacman/db.lck
|
||||
state: absent
|
||||
timeout: "{{ environment_pacman_lock_timeout }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Setup Pacman
|
||||
when:
|
||||
- not (custom_iso | bool)
|
||||
- item.os is not defined or os in item.os
|
||||
community.general.pacman:
|
||||
update_cache: true
|
||||
force: true
|
||||
name: "{{ item.name }}"
|
||||
state: latest
|
||||
loop:
|
||||
- { name: glibc }
|
||||
- { name: lua, os: [almalinux, fedora, rhel, rocky] }
|
||||
- { name: dnf, os: [almalinux, fedora, rhel, rocky] }
|
||||
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
|
||||
- { name: debian-archive-keyring, os: [debian] }
|
||||
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
retries: "{{ environment_pacman_retries }}"
|
||||
delay: "{{ environment_pacman_retry_delay }}"
|
||||
|
||||
- name: Prepare /iso mount and repository for RHEL-based systems
|
||||
when: os == "rhel"
|
||||
block:
|
||||
- name: Create /iso directory
|
||||
ansible.builtin.file:
|
||||
path: /usr/local/install/redhat/dvd
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Detect RHEL ISO device
|
||||
ansible.builtin.command: lsblk -rno NAME,TYPE
|
||||
register: environment_lsblk_result
|
||||
changed_when: false
|
||||
|
||||
- name: Select RHEL ISO device
|
||||
vars:
|
||||
_rom_devices: >-
|
||||
{{
|
||||
environment_lsblk_result.stdout_lines
|
||||
| map('split', ' ')
|
||||
| selectattr('1', 'equalto', 'rom')
|
||||
| map('first')
|
||||
| map('regex_replace', '^', '/dev/')
|
||||
| list
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
environment_rhel_iso_device: >-
|
||||
{{
|
||||
_rom_devices[-1]
|
||||
if _rom_devices | length > 1
|
||||
else (_rom_devices[0] | default('/dev/sr1'))
|
||||
}}
|
||||
|
||||
- name: Mount RHEL ISO
|
||||
ansible.posix.mount:
|
||||
src: "{{ environment_rhel_iso_device }}"
|
||||
path: /usr/local/install/redhat/dvd
|
||||
fstype: iso9660
|
||||
opts: "ro,loop"
|
||||
state: mounted
|
||||
|
||||
# Security note: RPM Sequoia signature policy is relaxed to allow
|
||||
# bootstrapping RHEL-family distros from the Arch ISO, where the
|
||||
# host rpm/dnf does not trust target distro GPG keys. Package
|
||||
# integrity is verified by the target system's own rpm after reboot.
|
||||
- name: Create RPM macros directory
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.file:
|
||||
path: /etc/rpm
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Relax RPM Sequoia signature policy for RHEL bootstrap
|
||||
when: is_rhel | bool
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/rpm/macros
|
||||
content: "%_pkgverify_level none\n"
|
||||
mode: "0644"
|
||||
|
||||
- name: Configure RHEL Repos for installation
|
||||
when: is_rhel | bool
|
||||
block:
|
||||
- name: Create directories for repository files and RPM GPG keys
|
||||
ansible.builtin.file:
|
||||
path: /etc/yum.repos.d
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create RHEL repository file
|
||||
ansible.builtin.template:
|
||||
src: "{{ os }}.repo.j2"
|
||||
dest: /etc/yum.repos.d/{{ os }}.repo
|
||||
mode: "0644"
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
- name: Check for third-party preparation tasks
|
||||
run_once: true
|
||||
become: false
|
||||
delegate_to: localhost
|
||||
vars:
|
||||
ansible_connection: local
|
||||
block:
|
||||
- name: Resolve third-party preparation task path
|
||||
ansible.builtin.set_fact:
|
||||
environment_thirdparty_tasks_path: >-
|
||||
{{
|
||||
thirdparty_tasks
|
||||
if thirdparty_tasks | regex_search('^/')
|
||||
else playbook_dir + '/' + thirdparty_tasks
|
||||
}}
|
||||
|
||||
- name: Stat third-party preparation tasks
|
||||
ansible.builtin.stat:
|
||||
path: "{{ environment_thirdparty_tasks_path }}"
|
||||
register: environment_thirdparty_tasks_stat
|
||||
|
||||
- name: Run third-party preparation tasks
|
||||
when:
|
||||
- thirdparty_tasks | length > 0
|
||||
- environment_thirdparty_tasks_stat.stat.exists
|
||||
ansible.builtin.include_tasks: "{{ environment_thirdparty_tasks_path }}"
|
||||
@@ -1,15 +1,130 @@
|
||||
---
|
||||
- name: Configure work environment
|
||||
become: "{{ (hypervisor_type | default('none')) != 'vmware' }}"
|
||||
- name: Configre work environment
|
||||
become: true
|
||||
block:
|
||||
- name: Detect and validate live environment
|
||||
ansible.builtin.include_tasks: _detect_live.yml
|
||||
- name: Wait for connection
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 60
|
||||
delay: 5
|
||||
|
||||
- name: Configure network and connectivity
|
||||
ansible.builtin.include_tasks: _configure_network.yml
|
||||
- name: Gather facts
|
||||
ansible.builtin.setup:
|
||||
|
||||
- name: Prepare installer environment
|
||||
ansible.builtin.include_tasks: _prepare_installer.yml
|
||||
- name: Check if host is booted from the Arch install media
|
||||
ansible.builtin.stat:
|
||||
path: /run/archiso
|
||||
register: archiso_stat
|
||||
|
||||
- name: Run third-party preparation tasks
|
||||
ansible.builtin.include_tasks: _thirdparty.yml
|
||||
- name: Abort if the host is not booted from the Arch install media
|
||||
ansible.builtin.fail:
|
||||
msg: This host is not booted from the Arch install media!
|
||||
when: not archiso_stat.stat.exists
|
||||
|
||||
- name: Setect Interface
|
||||
when: hypervisor == "vmware"
|
||||
ansible.builtin.shell: "set -o pipefail && ip l | awk -F': ' '!/lo/{print $2; exit}'"
|
||||
changed_when: interface_name.rc == 0
|
||||
register: interface_name
|
||||
|
||||
- name: Set IP-Address
|
||||
when: hypervisor == "vmware"
|
||||
ansible.builtin.command: "ip addr replace {{ ansible_host }}/{{ vm_nms | default(24) }} dev {{ interface_name.stdout }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Set Default Gateway
|
||||
when: hypervisor == "vmware"
|
||||
ansible.builtin.command: "ip route replace default via {{ vm_gw }}"
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Synchronize clock via NTP
|
||||
ansible.builtin.command: timedatectl set-ntp true
|
||||
changed_when: result.rc == 0
|
||||
register: result
|
||||
|
||||
- name: Configure SSH for root login
|
||||
when: hypervisor == "vmware" and vmware_ssh | bool
|
||||
block:
|
||||
- name: Allow empty passwords temporarily
|
||||
ansible.builtin.replace:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "^#?PermitEmptyPasswords.*"
|
||||
replace: "PermitEmptyPasswords yes"
|
||||
|
||||
- name: Allow root login
|
||||
ansible.builtin.replace:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "^#?PermitRootLogin.*"
|
||||
replace: "PermitRootLogin yes"
|
||||
|
||||
- name: Reload SSH service to apply changes
|
||||
ansible.builtin.service:
|
||||
name: sshd
|
||||
state: reloaded
|
||||
|
||||
- name: Set connection back to SSH
|
||||
ansible.builtin.set_fact:
|
||||
ansible_connection: ssh
|
||||
ansible_user: "root"
|
||||
ansible_password: ""
|
||||
ansible_become_password: ""
|
||||
ansible_ssh_extra_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
|
||||
|
||||
- name: Speed-up Bootstrap process
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/pacman.conf
|
||||
regexp: ^#ParallelDownloads =
|
||||
line: ParallelDownloads = 20
|
||||
|
||||
- name: Wait for Pacman
|
||||
ansible.builtin.wait_for:
|
||||
timeout: 15
|
||||
|
||||
- name: Setup Pacman
|
||||
community.general.pacman:
|
||||
update_cache: true
|
||||
force: true
|
||||
name: "{{ item.name }}"
|
||||
state: latest
|
||||
loop:
|
||||
- { name: glibc }
|
||||
- { name: dnf, os: [almalinux, fedora, rhel9, rhel8, rocky] }
|
||||
- { name: debootstrap, os: [debian11, debian12, ubuntu, ubuntu-lts] }
|
||||
- { name: debian-archive-keyring, os: [debian11, debian12] }
|
||||
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
|
||||
when: "'os' not in item or os in item.os"
|
||||
retries: 4
|
||||
delay: 15
|
||||
|
||||
- name: Prepare /iso mount and repository for RHEL-based systems
|
||||
when: os | lower in ["rhel8", "rhel9"]
|
||||
block:
|
||||
- name: Create /iso directory
|
||||
ansible.builtin.file:
|
||||
path: /usr/local/install/redhat/dvd
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Mount RHEL ISO
|
||||
ansible.posix.mount:
|
||||
src: "{{ '/dev/sr1' if hypervisor == 'vmware' else '/dev/sr2' }}"
|
||||
path: /usr/local/install/redhat/dvd
|
||||
fstype: iso9660
|
||||
opts: "ro,loop"
|
||||
state: mounted
|
||||
|
||||
- name: Configure RHEL Repos for installation
|
||||
when: os | lower in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"]
|
||||
block:
|
||||
- name: Create directories for repository files and RPM GPG keys
|
||||
ansible.builtin.file:
|
||||
path: /etc/yum.repos.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create RHEL repository file
|
||||
ansible.builtin.template:
|
||||
src: "{{ os | lower }}.repo.j2"
|
||||
dest: /etc/yum.repos.d/{{ os | lower }}.repo
|
||||
mode: '0644'
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[baseos]
|
||||
name=Rocky Linux $releasever - BaseOS
|
||||
mirrorlist=https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=BaseOS-$releasever
|
||||
#baseurl=http://dl.rockylinux.org/$contentdir/$releasever/BaseOS/$basearch/os/
|
||||
gpgcheck=1
|
||||
enabled=1
|
||||
countme=1
|
||||
gpgkey=https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-$releasever
|
||||
metadata_expire=86400
|
||||
enabled_metadata=1
|
||||
|
||||
[appstream]
|
||||
name=Rocky Linux $releasever - AppStream
|
||||
mirrorlist=https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=AppStream-$releasever
|
||||
#baseurl=http://dl.rockylinux.org/$contentdir/$releasever/AppStream/$basearch/os/
|
||||
gpgcheck=1
|
||||
enabled=1
|
||||
countme=1
|
||||
gpgkey=https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-$releasever
|
||||
metadata_expire=86400
|
||||
enabled_metadata=1
|
||||
@@ -1,191 +0,0 @@
|
||||
---
|
||||
# OS family lists — single source of truth for platform detection and validation
|
||||
os_family_rhel:
|
||||
- almalinux
|
||||
- fedora
|
||||
- rhel
|
||||
- rocky
|
||||
os_family_debian:
|
||||
- debian
|
||||
- ubuntu
|
||||
- ubuntu-lts
|
||||
|
||||
# OS → family mapping — aligns with the main project's ansible_os_family pattern.
|
||||
# Enables platform_config dict lookups per role instead of inline when: is_rhel chains.
|
||||
os_family_map:
|
||||
almalinux: RedHat
|
||||
alpine: Alpine
|
||||
archlinux: Archlinux
|
||||
debian: Debian
|
||||
fedora: RedHat
|
||||
opensuse: Suse
|
||||
rhel: RedHat
|
||||
rocky: RedHat
|
||||
ubuntu: Debian
|
||||
ubuntu-lts: Debian
|
||||
void: Void
|
||||
|
||||
os_supported:
|
||||
- almalinux
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian
|
||||
- fedora
|
||||
- opensuse
|
||||
- rhel
|
||||
- rocky
|
||||
- ubuntu
|
||||
- ubuntu-lts
|
||||
- void
|
||||
|
||||
# User input. Normalized into hypervisor_cfg + hypervisor_type.
|
||||
hypervisor:
|
||||
type: "none"
|
||||
hypervisor_defaults:
|
||||
type: "none"
|
||||
url: ""
|
||||
username: ""
|
||||
password: ""
|
||||
node: ""
|
||||
storage: ""
|
||||
datacenter: ""
|
||||
cluster: ""
|
||||
folder: ""
|
||||
certs: false
|
||||
ssh: false
|
||||
|
||||
physical_default_os: "archlinux"
|
||||
custom_iso: false
|
||||
thirdparty_tasks: "dropins/preparation.yml"
|
||||
|
||||
system_defaults:
|
||||
type: "virtual" # virtual|physical
|
||||
os: ""
|
||||
version: ""
|
||||
filesystem: "ext4"
|
||||
name: ""
|
||||
id: ""
|
||||
cpus: 0
|
||||
memory: 0 # MiB
|
||||
balloon: 0 # MiB
|
||||
network:
|
||||
bridge: ""
|
||||
vlan: ""
|
||||
ip: ""
|
||||
prefix: ""
|
||||
gateway: ""
|
||||
dns:
|
||||
servers: []
|
||||
search: []
|
||||
interfaces: []
|
||||
path: ""
|
||||
timezone: "Europe/Vienna"
|
||||
locale: "en_US.UTF-8"
|
||||
keymap: "us"
|
||||
mirror: ""
|
||||
packages: []
|
||||
disks: []
|
||||
users: {}
|
||||
root:
|
||||
password: ""
|
||||
shell: "/bin/bash"
|
||||
luks:
|
||||
enabled: false
|
||||
passphrase: ""
|
||||
mapper: "SYSTEM_DECRYPTED"
|
||||
auto: true
|
||||
method: "tpm2"
|
||||
tpm2:
|
||||
device: "auto"
|
||||
pcrs: ""
|
||||
keysize: 64
|
||||
options: "discard,tries=3"
|
||||
type: "luks2"
|
||||
cipher: "aes-xts-plain64"
|
||||
hash: "sha512"
|
||||
iter: 4000
|
||||
bits: 512
|
||||
pbkdf: "argon2id"
|
||||
urandom: true
|
||||
verify: true
|
||||
features:
|
||||
cis:
|
||||
enabled: false
|
||||
selinux:
|
||||
enabled: true
|
||||
firewall:
|
||||
enabled: true
|
||||
backend: "firewalld" # firewalld|ufw
|
||||
toolkit: "nftables" # nftables|iptables
|
||||
ssh:
|
||||
enabled: true
|
||||
zstd:
|
||||
enabled: true
|
||||
swap:
|
||||
enabled: true
|
||||
banner:
|
||||
motd: false
|
||||
sudo: true
|
||||
rhel_repo:
|
||||
source: "iso" # iso|satellite|none — how RHEL systems get packages post-install
|
||||
url: "" # Satellite/custom repo URL when source=satellite
|
||||
aur:
|
||||
enabled: false
|
||||
helper: "yay" # yay|paru
|
||||
user: "_aur_builder"
|
||||
chroot:
|
||||
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn
|
||||
initramfs:
|
||||
generator: "" # auto-detected; override: dracut|mkinitcpio|initramfs-tools
|
||||
desktop:
|
||||
enabled: false
|
||||
environment: "" # gnome|kde|xfce|sway|hyprland|cinnamon|mate|lxqt|budgie
|
||||
display_manager: "" # auto from environment when empty; override: gdm|sddm|lightdm|greetd
|
||||
secure_boot:
|
||||
enabled: false
|
||||
method: "" # arch only: sbctl (default) or uki; ignored for other distros
|
||||
|
||||
# Per-hypervisor required fields — drives data-driven validation.
|
||||
# All virtual types additionally require network bridge or interfaces.
|
||||
hypervisor_required_fields:
|
||||
proxmox:
|
||||
hypervisor: [url, username, password, node, storage]
|
||||
system: [id]
|
||||
vmware:
|
||||
hypervisor: [url, username, password, datacenter, storage]
|
||||
system: []
|
||||
xen:
|
||||
hypervisor: []
|
||||
system: []
|
||||
libvirt:
|
||||
hypervisor: []
|
||||
system: []
|
||||
|
||||
# Hypervisor-to-disk device prefix mapping for virtual machines.
|
||||
# Physical installs must set system.disks[].device explicitly.
|
||||
hypervisor_disk_device_map:
|
||||
libvirt: "/dev/vd"
|
||||
xen: "/dev/xvd"
|
||||
proxmox: "/dev/sd"
|
||||
vmware: "/dev/sd"
|
||||
|
||||
# Mountpoints managed by the partitioning role — forbidden for extra disks.
|
||||
reserved_mounts:
|
||||
- /boot
|
||||
- /boot/efi
|
||||
- /home
|
||||
- /var
|
||||
- /var/log
|
||||
- /var/log/audit
|
||||
|
||||
# Drive letter sequence for disk device naming (max 26 disks).
|
||||
disk_letter_map: "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
system_disk_defaults:
|
||||
size: 0
|
||||
device: ""
|
||||
mount:
|
||||
path: ""
|
||||
fstype: ""
|
||||
label: ""
|
||||
opts: "defaults"
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
- name: Normalize system disks input
|
||||
vars:
|
||||
system_disks: "{{ system_cfg.disks | default([]) }}"
|
||||
system_disk_letter_map: "{{ disk_letter_map }}"
|
||||
system_disk_device_prefix: >-
|
||||
{{
|
||||
hypervisor_disk_device_map.get(hypervisor_type, '')
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
}}
|
||||
block:
|
||||
- name: Validate system disks structure
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- system_disks is sequence
|
||||
- (system_disks | length) <= 26
|
||||
fail_msg: "system.disks must be a list with at most 26 entries."
|
||||
quiet: true
|
||||
|
||||
- name: Validate system disk entries
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item is mapping
|
||||
- item.mount is not defined or item.mount is mapping
|
||||
fail_msg: "Each disk entry must be a dictionary, and disk.mount (if set) must be a dictionary."
|
||||
quiet: true
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Initialize normalized disk list
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: []
|
||||
|
||||
- name: Build normalized system disk configuration
|
||||
vars:
|
||||
disk_idx: "{{ ansible_loop.index0 }}"
|
||||
disk_letter: "{{ system_disk_letter_map[disk_idx] }}"
|
||||
disk_cfg_base: "{{ system_disk_defaults | combine(item, recursive=True) }}"
|
||||
disk_mount: "{{ system_disk_defaults.mount | combine((disk_cfg_base.mount | default({})), recursive=True) }}"
|
||||
disk_mount_path: "{{ (disk_mount.path | default('') | string) | trim }}"
|
||||
disk_mount_fstype: >-
|
||||
{{
|
||||
disk_mount.fstype
|
||||
if (disk_mount.fstype | default('') | string | length) > 0
|
||||
else ('ext4' if disk_mount_path | length > 0 else '')
|
||||
}}
|
||||
disk_device: >-
|
||||
{{
|
||||
disk_cfg_base.device
|
||||
if (disk_cfg_base.device | string | length) > 0
|
||||
else (
|
||||
(system_disk_device_prefix ~ disk_letter)
|
||||
if system_cfg.type == 'virtual'
|
||||
else ''
|
||||
)
|
||||
}}
|
||||
disk_partition: >-
|
||||
{{
|
||||
disk_device ~ ('p1' if (disk_device | regex_search('\\d$')) else '1')
|
||||
if disk_device | length > 0
|
||||
else ''
|
||||
}}
|
||||
ansible.builtin.set_fact:
|
||||
system_disks_cfg: >-
|
||||
{{
|
||||
system_disks_cfg + [
|
||||
disk_cfg_base
|
||||
| combine(
|
||||
{
|
||||
'device': disk_device,
|
||||
'mount': {
|
||||
'path': disk_mount_path,
|
||||
'fstype': disk_mount_fstype,
|
||||
'label': disk_mount.label | default('') | string,
|
||||
'opts': disk_mount.opts | default('defaults') | string
|
||||
},
|
||||
'partition': disk_partition
|
||||
},
|
||||
recursive=True
|
||||
)
|
||||
]
|
||||
}}
|
||||
loop: "{{ system_disks }}"
|
||||
loop_control:
|
||||
loop_var: item
|
||||
extended: true
|
||||
label: "{{ item | to_json }}"
|
||||
|
||||
- name: Update system configuration with normalized disks
|
||||
ansible.builtin.set_fact:
|
||||
system_cfg: "{{ system_cfg | combine({'disks': system_disks_cfg}, recursive=True) }}"
|
||||
|
||||
- name: Set install_drive from primary disk
|
||||
when:
|
||||
- system_disks_cfg | length > 0
|
||||
- system_disks_cfg[0].device | string | length > 0
|
||||
ansible.builtin.set_fact:
|
||||
install_drive: "{{ system_disks_cfg[0].device }}"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user