Compare commits
161 Commits
master
...
c73fe22137
| Author | SHA1 | Date | |
|---|---|---|---|
| c73fe22137 | |||
| 868cdea9b2 | |||
| 0c9ccbf28a | |||
| 225f9967de | |||
| 7376383324 | |||
| 39043b45ee | |||
| 31b06f1f56 | |||
| 6015a9bc4e | |||
| 4f9893ff6d | |||
| ba46c3a157 | |||
| 4e8dc929a8 | |||
| b59746c463 | |||
| 2085fc1696 | |||
| 7227ca1aa0 | |||
| a6300a5467 | |||
| b224f0780e | |||
| c9bc8cb624 | |||
| 89ddfedd52 | |||
| 44aa6ac369 | |||
| 13faf33296 | |||
| de451be77b | |||
| af9f264cd3 | |||
| 750a085e19 | |||
| f2eb9f2c8e | |||
| 79988619c6 | |||
| fb69c96e4a | |||
| d586c087f8 | |||
| 9dd71b2559 | |||
| 35f1702447 | |||
| 8b18fbdb4c | |||
| 909a0a6021 | |||
| 2f3fce42b5 | |||
| b72816e985 | |||
| ac0b5caf83 | |||
| 3ddc3c72ed | |||
| f1af7ccbca | |||
| 51ca969ff4 | |||
| 1221249546 | |||
| 87fd69b825 | |||
| 3deb3ea751 | |||
| cc30637f09 | |||
| 23721aac96 | |||
| 5a9b346d72 | |||
| 75267e5140 | |||
| f0fb68992d | |||
| 0e3edb41f7 | |||
| 2bf0cb901e | |||
| 1216c79619 | |||
| 4efd64664d | |||
| dc5aa5077e | |||
| c65934c290 | |||
| 5b8438ac3b | |||
| 45df803131 | |||
| 30f74fa4bd | |||
| 19372db27e | |||
| d55fc5799d | |||
| 98231be0bd | |||
| c46a4a5a0a | |||
| b84688f1d6 | |||
| b1d2294d63 | |||
| ac339b54c4 | |||
| cb46a6989f | |||
| 73ea7a177b | |||
| 0f8faf0a22 | |||
| b520126253 | |||
| a4ca4c4ff4 | |||
| d9efb54bec | |||
| e7a0cc4f62 | |||
| a76f317f8f | |||
| e5bd152fb3 | |||
| 6d1c3577df | |||
| 86f0284acb | |||
| 221bb4d517 | |||
| e81ba76446 | |||
| 54bbb9d15c | |||
| f94b220020 | |||
| 3fd470d63e | |||
| a3cd507b2a | |||
| f74ec325ea | |||
| bef15af69f | |||
| 7970d933e8 | |||
| a123a32feb | |||
| 54c704de4e | |||
| 9308d09d7b | |||
| f367844239 | |||
| 53e4499d2b | |||
| eb63a4fa83 | |||
| 9e3688ae2b | |||
| dea01cc8a0 | |||
| 92c9702e1d | |||
| c837a52a24 | |||
| fbd57e0603 | |||
| 40a9ee9882 | |||
| 3448e95e5c | |||
| 074831833f | |||
| d1a5217e88 | |||
| 07492b5b57 | |||
| 14913bcd3d | |||
| 041650c287 | |||
| a63ffbc731 | |||
| 9d2f1cc5bd | |||
| f72f9feb9a | |||
| 417737f904 | |||
| a06c2ebdcf | |||
| e174ecda42 | |||
| 5246a905bb | |||
| d00d84b69c | |||
| 4dafa8c596 | |||
| 53584b8730 | |||
| ce40468b77 | |||
| 4b4fab3c33 | |||
| db2fab5e7d | |||
| 42be0a5919 | |||
| 17400fa6ff | |||
| deb14d2c94 | |||
| 65c5b1029b | |||
| a1fbb7c21d | |||
| d076ac8fef | |||
| c82e4afc4d | |||
| ac72fdc4a6 | |||
| b2e050c467 | |||
| 914d7dd9d1 | |||
| 21bf8f79e2 | |||
| 38feff4369 | |||
| 404529e8a4 | |||
| 3db18858c3 | |||
| 72a9576abe | |||
| 462c2c7dfe | |||
| ef8bfeaf84 | |||
| ba6be037ac | |||
| 5ca1c7f570 | |||
| cd8e477534 | |||
| c439e9741e | |||
| 0a5c70e49f | |||
| 19f2c9efe2 | |||
| 230c74fd9b | |||
| a2c19e2e49 | |||
| 9f9a4b38b8 | |||
| 524356cf8d | |||
| a2993212ca | |||
| fba2e5fc94 | |||
| cf68a93b45 | |||
| 3000268a0e | |||
| 196c5be67a | |||
| 33bad193b4 | |||
| d5277802f7 | |||
| 28e6cf50d1 | |||
| 42cb5071c2 | |||
| 23a798a63a | |||
| 5dd84c6b39 | |||
| d0ae20911b | |||
| b6d06dd96d | |||
| 09b3ed44ba | |||
| 603abe63cb | |||
| 1c0e6533ae | |||
| 00aa614cfd | |||
| 4905d10bc0 | |||
| b4e8ccb77f | |||
| 2a82ee4d5c | |||
| 7b213e7456 | |||
| cfc261878a |
@@ -1,6 +1,5 @@
|
|||||||
skip_list:
|
skip_list:
|
||||||
- run-once
|
- run-once
|
||||||
- var-naming[no-role-prefix] # user-facing API dicts (cis, system, hypervisor) are intentionally not role-prefixed
|
- var-naming[no-role-prefix] # user-facing API dicts (cis, system, hypervisor) are intentionally not role-prefixed
|
||||||
- args[module] # false positives from variable-based module_defaults (_proxmox_auth, _vmware_auth)
|
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- roles/global_defaults/
|
- roles/global_defaults/
|
||||||
|
|||||||
55
README.md
55
README.md
@@ -202,29 +202,14 @@ When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway`
|
|||||||
|
|
||||||
#### `system.users`
|
#### `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 |
|
| Key | Type | Default | Description |
|
||||||
| ---------- | ----------- | ------- | -------------------------------------------------- |
|
| ---------- | ----------- | ------- | -------------------------------------------------- |
|
||||||
| *(dict key)* | string | -- | Username (required) |
|
| `name` | string | -- | Username (required) |
|
||||||
| `password` | string | -- | User password (required for at least one user) |
|
| `password` | string | -- | User password (required for first user) |
|
||||||
| `keys` | list | `[]` | SSH public keys |
|
| `keys` | list | `[]` | SSH public keys |
|
||||||
| `sudo` | bool/string | -- | `true` for NOPASSWD ALL, or custom sudoers string |
|
| `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`.
|
The first user's credentials are prompted interactively via `vars_prompt` unless supplied in inventory or `-e`.
|
||||||
|
|
||||||
#### `system.root`
|
#### `system.root`
|
||||||
|
|
||||||
@@ -257,15 +242,7 @@ Users must be defined in inventory. The dict format enables additive merging acr
|
|||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| -------- | ------------- | ------- | ---------------------------------------------- |
|
| -------- | ------------- | ------- | ---------------------------------------------- |
|
||||||
| `device` | string | `auto` | TPM2 device selector |
|
| `device` | string | `auto` | TPM2 device selector |
|
||||||
| `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`); empty = no PCR binding |
|
| `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`) |
|
||||||
|
|
||||||
**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`
|
#### `system.features`
|
||||||
|
|
||||||
@@ -282,26 +259,6 @@ The bootstrap auto-switches to dracut when `method: tpm2` is set. Override via `
|
|||||||
| `banner.motd` | bool | `false` | MOTD banner |
|
| `banner.motd` | bool | `false` | MOTD banner |
|
||||||
| `banner.sudo` | bool | `true` | Sudo banner |
|
| `banner.sudo` | bool | `true` | Sudo banner |
|
||||||
| `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, or `systemd-nspawn` |
|
| `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
|
### 4.3 `hypervisor` Dictionary
|
||||||
|
|
||||||
@@ -441,7 +398,7 @@ ansible-playbook -i inventory.yml main.yml
|
|||||||
ansible-playbook -i inventory.yml main.yml -e @vars.yml
|
ansible-playbook -i inventory.yml main.yml -e @vars.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
All credentials (`system.users`, `system.root.password`) must be defined in inventory or passed via `-e`.
|
Credentials for the first user and root are prompted interactively via `vars_prompt` unless already set in inventory or passed via `-e`.
|
||||||
|
|
||||||
Example inventory files are included:
|
Example inventory files are included:
|
||||||
|
|
||||||
@@ -451,7 +408,7 @@ Example inventory files are included:
|
|||||||
|
|
||||||
## 7. Security
|
## 7. Security
|
||||||
|
|
||||||
Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, user passwords in `system.users`, `system.root.password`).
|
Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, `system.users[].password`, `system.root.password`).
|
||||||
|
|
||||||
## 8. Safety
|
## 8. Safety
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ hash_behaviour = merge
|
|||||||
interpreter_python = auto_silent
|
interpreter_python = auto_silent
|
||||||
deprecation_warnings = False
|
deprecation_warnings = False
|
||||||
host_key_checking = False
|
host_key_checking = False
|
||||||
|
|
||||||
[ssh_connection]
|
|
||||||
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=30 -o ServerAliveCountMax=10
|
|
||||||
|
|||||||
100
main.yml
100
main.yml
@@ -14,7 +14,94 @@
|
|||||||
strategy: free # noqa: run-once[play]
|
strategy: free # noqa: run-once[play]
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
become: true
|
become: true
|
||||||
|
vars_prompt:
|
||||||
|
- name: user_name
|
||||||
|
prompt: |
|
||||||
|
What is your username?
|
||||||
|
private: false
|
||||||
|
|
||||||
|
- name: user_public_key
|
||||||
|
prompt: |
|
||||||
|
What is your ssh key?
|
||||||
|
private: false
|
||||||
|
|
||||||
|
- name: user_password
|
||||||
|
prompt: |
|
||||||
|
What is your password?
|
||||||
|
confirm: true
|
||||||
|
|
||||||
|
- name: root_password
|
||||||
|
prompt: |
|
||||||
|
What is your root password?
|
||||||
|
confirm: true
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
|
- name: Apply prompted authentication values to system input
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
system_input: "{{ system | default({}) }}"
|
||||||
|
system_users_input: "{{ system_input.users | default([]) }}"
|
||||||
|
system_first_user: >-
|
||||||
|
{{
|
||||||
|
system_users_input[0]
|
||||||
|
if (system_users_input is iterable and system_users_input is not string
|
||||||
|
and system_users_input is not mapping and system_users_input | length > 0)
|
||||||
|
else {}
|
||||||
|
}}
|
||||||
|
system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}"
|
||||||
|
prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}"
|
||||||
|
prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string | trim }}"
|
||||||
|
prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
|
||||||
|
prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}"
|
||||||
|
resolved_user:
|
||||||
|
name: >-
|
||||||
|
{{
|
||||||
|
system_first_user.name | string
|
||||||
|
if (system_first_user.name | default('') | string | length) > 0
|
||||||
|
else prompt_user_name
|
||||||
|
}}
|
||||||
|
keys: >-
|
||||||
|
{{
|
||||||
|
system_first_user['keys']
|
||||||
|
if (system_first_user['keys'] is defined
|
||||||
|
and system_first_user['keys'] is iterable
|
||||||
|
and system_first_user['keys'] is not string
|
||||||
|
and system_first_user['keys'] | length > 0)
|
||||||
|
else (
|
||||||
|
[prompt_user_key]
|
||||||
|
if (prompt_user_key | length > 0)
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
password: >-
|
||||||
|
{{
|
||||||
|
system_first_user.password | string
|
||||||
|
if (system_first_user.password | default('') | string | length) > 0
|
||||||
|
else prompt_user_password
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system: >-
|
||||||
|
{{
|
||||||
|
system_input
|
||||||
|
| combine(
|
||||||
|
{
|
||||||
|
'users': (
|
||||||
|
[resolved_user]
|
||||||
|
+ (system_users_input[1:]
|
||||||
|
if (system_users_input is sequence
|
||||||
|
and system_users_input is not string
|
||||||
|
and system_users_input | length > 1)
|
||||||
|
else [])
|
||||||
|
),
|
||||||
|
'root': {
|
||||||
|
'password': (
|
||||||
|
(system_root_input.password | default('') | string | length) > 0
|
||||||
|
) | ternary(system_root_input.password | string, prompt_root_password)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
recursive=True
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
- name: Load global defaults
|
- name: Load global defaults
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: global_defaults
|
name: global_defaults
|
||||||
@@ -73,6 +160,8 @@
|
|||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: cleanup
|
name: cleanup
|
||||||
public: true
|
public: true
|
||||||
|
vars:
|
||||||
|
ansible_become: false
|
||||||
|
|
||||||
rescue:
|
rescue:
|
||||||
- name: Delete VM on bootstrap failure
|
- name: Delete VM on bootstrap failure
|
||||||
@@ -119,15 +208,10 @@
|
|||||||
when:
|
when:
|
||||||
- post_reboot_can_connect | bool
|
- post_reboot_can_connect | bool
|
||||||
no_log: true
|
no_log: true
|
||||||
vars:
|
|
||||||
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_connection: ssh
|
ansible_user: "{{ system_cfg.users[0].name }}"
|
||||||
ansible_host: "{{ system_cfg.network.ip }}"
|
ansible_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_port: 22
|
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_user: "{{ _primary.key }}"
|
|
||||||
ansible_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_become_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -18,9 +18,6 @@
|
|||||||
groupinstall -y {{ _dnf_groups }}
|
groupinstall -y {{ _dnf_groups }}
|
||||||
register: bootstrap_dnf_base_result
|
register: bootstrap_dnf_base_result
|
||||||
changed_when: bootstrap_dnf_base_result.rc == 0
|
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
|
- name: Ensure chroot has DNS resolution
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -55,11 +55,6 @@
|
|||||||
register: bootstrap_debian_update_result
|
register: bootstrap_debian_update_result
|
||||||
changed_when: bootstrap_debian_update_result.rc == 0
|
changed_when: bootstrap_debian_update_result.rc == 0
|
||||||
|
|
||||||
- name: Upgrade all packages to latest versions
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
|
||||||
register: bootstrap_debian_upgrade_result
|
|
||||||
changed_when: "'0 upgraded' not in bootstrap_debian_upgrade_result.stdout"
|
|
||||||
|
|
||||||
- name: Install extra packages
|
- name: Install extra packages
|
||||||
when: bootstrap_debian_extra_args | trim | length > 0
|
when: bootstrap_debian_extra_args | trim | length > 0
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
|
||||||
|
|||||||
@@ -34,10 +34,6 @@
|
|||||||
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
|
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
|
||||||
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
|
||||||
|
|
||||||
- name: Install desktop environment packages
|
|
||||||
when: system_cfg.features.desktop.enabled | bool
|
|
||||||
ansible.builtin.include_tasks: _desktop.yml
|
|
||||||
|
|
||||||
- name: Ensure chroot uses live environment DNS
|
- name: Ensure chroot uses live environment DNS
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
src: /run/NetworkManager/resolv.conf
|
src: /run/NetworkManager/resolv.conf
|
||||||
|
|||||||
@@ -54,11 +54,6 @@
|
|||||||
register: bootstrap_ubuntu_update_result
|
register: bootstrap_ubuntu_update_result
|
||||||
changed_when: bootstrap_ubuntu_update_result.rc == 0
|
changed_when: bootstrap_ubuntu_update_result.rc == 0
|
||||||
|
|
||||||
- name: Upgrade all packages to latest versions
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt full-upgrade -y"
|
|
||||||
register: bootstrap_ubuntu_upgrade_result
|
|
||||||
changed_when: "'0 upgraded' not in bootstrap_ubuntu_upgrade_result.stdout"
|
|
||||||
|
|
||||||
- name: Install extra packages
|
- name: Install extra packages
|
||||||
when: bootstrap_ubuntu_extra_args | trim | length > 0
|
when: bootstrap_ubuntu_extra_args | trim | length > 0
|
||||||
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra_args }}"
|
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra_args }}"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -201,7 +201,6 @@ bootstrap_debian:
|
|||||||
- lrzsz
|
- lrzsz
|
||||||
- mtr
|
- mtr
|
||||||
- ncdu
|
- ncdu
|
||||||
- needrestart
|
|
||||||
- net-tools
|
- net-tools
|
||||||
- network-manager
|
- network-manager
|
||||||
- python-is-python3
|
- python-is-python3
|
||||||
@@ -222,7 +221,6 @@ bootstrap_debian:
|
|||||||
+ (['software-properties-common'] if (os_version | string) not in ['13', 'unstable'] 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 [])
|
+ (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else [])
|
||||||
+ (['tldr'] if (os_version | string) not in ['13', 'unstable'] 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_common_conditional
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -264,7 +262,6 @@ bootstrap_ubuntu:
|
|||||||
- mtr
|
- mtr
|
||||||
- ncdu
|
- ncdu
|
||||||
- ncurses-term
|
- ncurses-term
|
||||||
- needrestart
|
|
||||||
- net-tools
|
- net-tools
|
||||||
- network-manager
|
- network-manager
|
||||||
- python-is-python3
|
- python-is-python3
|
||||||
@@ -286,7 +283,6 @@ bootstrap_ubuntu:
|
|||||||
conditional: >-
|
conditional: >-
|
||||||
{{
|
{{
|
||||||
(['tldr'] if (os_version | default('') | string | length) > 0 else [])
|
(['tldr'] if (os_version | default('') | string | length) > 0 else [])
|
||||||
+ (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
|
|
||||||
+ bootstrap_common_conditional
|
+ bootstrap_common_conditional
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -325,7 +321,6 @@ bootstrap_archlinux:
|
|||||||
{{
|
{{
|
||||||
(['openssh'] if system_cfg.features.ssh.enabled | bool else [])
|
(['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 [])
|
+ (['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_common_conditional | reject('equalto', 'nftables') | list)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
- name: Initialize cleaned VM XML
|
- name: Initialize cleaned VM XML
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove boot ISO device from VM XML (source match)
|
- name: Remove boot ISO device from VM XML (source match)
|
||||||
when: boot_iso is defined and boot_iso | length > 0
|
when: boot_iso is defined and boot_iso | length > 0
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
when: boot_iso is defined and boot_iso | length > 0
|
when: boot_iso is defined and boot_iso | length > 0
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove boot ISO device from VM XML (target fallback)
|
- name: Remove boot ISO device from VM XML (target fallback)
|
||||||
community.general.xml:
|
community.general.xml:
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
- name: Update cleaned VM XML after removing boot ISO
|
- name: Update cleaned VM XML after removing boot ISO
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove cloud-init ISO device from VM XML (source match)
|
- name: Remove cloud-init ISO device from VM XML (source match)
|
||||||
community.general.xml:
|
community.general.xml:
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
- name: Update cleaned VM XML after removing cloud-init ISO source match
|
- name: Update cleaned VM XML after removing cloud-init ISO source match
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove cloud-init ISO device from VM XML (target fallback)
|
- name: Remove cloud-init ISO device from VM XML (target fallback)
|
||||||
community.general.xml:
|
community.general.xml:
|
||||||
@@ -60,6 +64,7 @@
|
|||||||
- name: Update cleaned VM XML after removing cloud-init ISO
|
- name: Update cleaned VM XML after removing cloud-init ISO
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit.xmlstring }}"
|
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit.xmlstring }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Strip XML declaration for libvirt define
|
- name: Strip XML declaration for libvirt define
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@@ -71,12 +76,7 @@
|
|||||||
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
|
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
|
||||||
| trim
|
| trim
|
||||||
}}
|
}}
|
||||||
|
changed_when: false
|
||||||
- name: Ensure boot device is set to hard disk in VM XML
|
|
||||||
when: "'<boot ' not in cleanup_libvirt_domain_xml_clean"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cleanup_libvirt_domain_xml_clean: >-
|
|
||||||
{{ cleanup_libvirt_domain_xml_clean | regex_replace('(</type>)', '\1\n <boot dev="hd"/>') }}
|
|
||||||
|
|
||||||
- name: Update VM definition without installer media
|
- name: Update VM definition without installer media
|
||||||
community.libvirt.virt:
|
community.libvirt.virt:
|
||||||
@@ -94,35 +94,6 @@
|
|||||||
state: destroyed
|
state: destroyed
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Enroll Secure Boot keys in VM NVRAM
|
|
||||||
when:
|
|
||||||
- system_cfg.features.secure_boot.enabled | default(false) | bool
|
|
||||||
- os != 'archlinux'
|
|
||||||
block:
|
|
||||||
- name: Find VM NVRAM file path
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
virsh -c {{ libvirt_uri | default('qemu:///system') }} dumpxml {{ hostname }}
|
|
||||||
| grep -oP '<nvram[^>]*>\K[^<]+'
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _sb_nvram_path
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Enroll Secure Boot keys via virt-fw-vars
|
|
||||||
when: _sb_nvram_path.stdout | default('') | length > 0
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- virt-fw-vars
|
|
||||||
- --inplace
|
|
||||||
- "{{ _sb_nvram_path.stdout | trim }}"
|
|
||||||
- --enroll-redhat
|
|
||||||
- --secure-boot
|
|
||||||
register: _sb_enroll_result
|
|
||||||
changed_when: _sb_enroll_result.rc == 0
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Start the VM
|
- name: Start the VM
|
||||||
community.libvirt.virt:
|
community.libvirt.virt:
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
|
|||||||
@@ -25,4 +25,3 @@
|
|||||||
community.proxmox.proxmox_kvm:
|
community.proxmox.proxmox_kvm:
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
state: restarted
|
state: restarted
|
||||||
no_log: true
|
|
||||||
|
|||||||
@@ -7,11 +7,34 @@
|
|||||||
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
|
||||||
block:
|
block:
|
||||||
- name: Ensure Xen disk definitions exist
|
- name: Ensure Xen disk definitions exist
|
||||||
ansible.builtin.include_tasks: ../../virtualization/tasks/_xen_disks.yml
|
when: virtualization_xen_disks is not defined
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cleanup_xen_disks: "{{ cleanup_xen_disks | default([]) + [cleanup_xen_disk_cfg] }}"
|
||||||
|
vars:
|
||||||
|
device_letter_map: "{{ disk_letter_map }}"
|
||||||
|
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
|
||||||
|
cleanup_xen_disk_cfg: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'path': (
|
||||||
|
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
|
||||||
|
if ansible_loop.index0 == 0
|
||||||
|
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
|
||||||
|
),
|
||||||
|
'target': 'xvd' ~ device_letter,
|
||||||
|
'size': (item.size | float)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
loop: "{{ system_cfg.disks }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item | to_json }}"
|
||||||
|
extended: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Render Xen VM configuration without installer media
|
- name: Render Xen VM configuration without installer media
|
||||||
vars:
|
vars:
|
||||||
xen_installer_media_enabled: false
|
xen_installer_media_enabled: false
|
||||||
|
virtualization_xen_disks: "{{ virtualization_xen_disks | default(cleanup_xen_disks | default([])) }}"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: xen.cfg.j2
|
src: xen.cfg.j2
|
||||||
dest: /tmp/xen-{{ hostname }}.cfg
|
dest: /tmp/xen-{{ hostname }}.cfg
|
||||||
|
|||||||
@@ -14,12 +14,3 @@
|
|||||||
- name: Set platform configuration
|
- name: Set platform configuration
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
_configuration_platform: "{{ configuration_platform_config[os_family] }}"
|
_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'}) }}
|
|
||||||
|
|||||||
@@ -34,16 +34,6 @@
|
|||||||
register: configuration_efi_entry_result
|
register: configuration_efi_entry_result
|
||||||
changed_when: configuration_efi_entry_result.rc == 0
|
changed_when: configuration_efi_entry_result.rc == 0
|
||||||
|
|
||||||
- name: Set installed OS as first EFI boot entry
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >-
|
|
||||||
set -o pipefail &&
|
|
||||||
efibootmgr | grep -i '{{ _efi_vendor }}' | grep -oP 'Boot\K[0-9A-F]+' | head -1
|
|
||||||
| xargs -I{} efibootmgr -o {}
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _efi_bootorder_result
|
|
||||||
changed_when: _efi_bootorder_result.rc == 0
|
|
||||||
|
|
||||||
- name: Ensure lvm2 for non btrfs filesystems
|
- name: Ensure lvm2 for non btrfs filesystems
|
||||||
when: os == "archlinux" and system_cfg.filesystem != "btrfs"
|
when: os == "archlinux" and system_cfg.filesystem != "btrfs"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
@@ -58,62 +48,16 @@
|
|||||||
register: configuration_initramfs_result
|
register: configuration_initramfs_result
|
||||||
changed_when: configuration_initramfs_result.rc == 0
|
changed_when: configuration_initramfs_result.rc == 0
|
||||||
|
|
||||||
- name: Generate grub config (RedHat)
|
- name: Generate grub config
|
||||||
when: os_family == 'RedHat'
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} /usr/sbin/{{ _configuration_platform.grub_mkconfig_prefix }}
|
|
||||||
-o /boot/grub2/grub.cfg
|
|
||||||
register: configuration_grub_result
|
|
||||||
changed_when: configuration_grub_result.rc == 0
|
|
||||||
|
|
||||||
- name: Fix btrfs BLS boot variable in grub config
|
|
||||||
when:
|
|
||||||
- os_family == 'RedHat'
|
|
||||||
- system_cfg.filesystem == 'btrfs'
|
|
||||||
ansible.builtin.replace:
|
|
||||||
path: /mnt/boot/grub2/grub.cfg
|
|
||||||
regexp: 'search --no-floppy --fs-uuid --set=boot \S+'
|
|
||||||
replace: 'set boot=$root'
|
|
||||||
|
|
||||||
- name: Create EFI grub.cfg wrapper for RedHat
|
|
||||||
when: os_family == 'RedHat'
|
|
||||||
vars:
|
vars:
|
||||||
_grub2_path: >-
|
configuration_grub_cfg_cmd: >-
|
||||||
{{
|
{{
|
||||||
'/grub2'
|
'/usr/sbin/' + _configuration_platform.grub_mkconfig_prefix + ' -o '
|
||||||
if (partitioning_separate_boot | bool)
|
+ partitioning_efi_mountpoint
|
||||||
else ('/@/boot/grub2' if system_cfg.filesystem == 'btrfs' else '/boot/grub2')
|
+ '/EFI/' + _efi_vendor + '/grub.cfg'
|
||||||
|
if os_family == 'RedHat'
|
||||||
|
else '/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg'
|
||||||
}}
|
}}
|
||||||
ansible.builtin.shell:
|
ansible.builtin.command: "{{ chroot_command }} {{ configuration_grub_cfg_cmd }}"
|
||||||
cmd: |
|
|
||||||
set -o pipefail
|
|
||||||
uuid=$(grep -m1 'search.*--set=root' /mnt/boot/grub2/grub.cfg | grep -oP '[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}')
|
|
||||||
cat > /mnt{{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/grub.cfg <<GRUBEOF
|
|
||||||
search --no-floppy --fs-uuid --set=dev $uuid
|
|
||||||
set prefix=(\$dev){{ _grub2_path }}
|
|
||||||
export \$prefix
|
|
||||||
configfile \$prefix/grub.cfg
|
|
||||||
GRUBEOF
|
|
||||||
executable: /bin/bash
|
|
||||||
register: _grub_wrapper_result
|
|
||||||
changed_when: _grub_wrapper_result.rc == 0
|
|
||||||
|
|
||||||
- name: Generate grub config (non-RedHat)
|
|
||||||
when: os_family != 'RedHat'
|
|
||||||
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg"
|
|
||||||
register: configuration_grub_result
|
register: configuration_grub_result
|
||||||
changed_when: configuration_grub_result.rc == 0
|
changed_when: configuration_grub_result.rc == 0
|
||||||
|
|
||||||
- name: Rebuild GRUB as standalone EFI for Secure Boot
|
|
||||||
when:
|
|
||||||
- system_cfg.features.secure_boot.enabled | default(false) | bool
|
|
||||||
- os == 'archlinux'
|
|
||||||
ansible.builtin.command: >-
|
|
||||||
{{ chroot_command }} grub-mkstandalone
|
|
||||||
-d /usr/lib/grub/x86_64-efi
|
|
||||||
-O x86_64-efi
|
|
||||||
--disable-shim-lock
|
|
||||||
-o {{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/grubx64.efi
|
|
||||||
boot/grub/grub.cfg=/boot/grub/grub.cfg
|
|
||||||
register: _grub_standalone_result
|
|
||||||
changed_when: _grub_standalone_result.rc == 0
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
block:
|
block:
|
||||||
- name: Set LUKS configuration facts
|
- name: Set LUKS configuration facts
|
||||||
vars:
|
vars:
|
||||||
_raw_pcrs: >-
|
luks_tpm2_pcrs: >-
|
||||||
{{
|
{{
|
||||||
(
|
(
|
||||||
system_cfg.luks.tpm2.pcrs
|
system_cfg.luks.tpm2.pcrs
|
||||||
@@ -20,17 +20,6 @@
|
|||||||
| regex_replace('\\s+', '')
|
| regex_replace('\\s+', '')
|
||||||
| regex_replace('^\\+|\\+$', '')
|
| regex_replace('^\\+|\\+$', '')
|
||||||
}}
|
}}
|
||||||
_sb_pcr7_safe: >-
|
|
||||||
{{
|
|
||||||
system_cfg.features.secure_boot.enabled | bool
|
|
||||||
and system_cfg.type | default('virtual') != 'virtual'
|
|
||||||
}}
|
|
||||||
luks_tpm2_pcrs: >-
|
|
||||||
{{
|
|
||||||
_raw_pcrs
|
|
||||||
if _raw_pcrs | length > 0
|
|
||||||
else ('7' if (_sb_pcr7_safe | bool) else '')
|
|
||||||
}}
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
configuration_luks_mapper_name: "{{ system_cfg.luks.mapper }}"
|
configuration_luks_mapper_name: "{{ system_cfg.luks.mapper }}"
|
||||||
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
|
||||||
@@ -47,12 +36,6 @@
|
|||||||
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
|
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
|
||||||
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
|
||||||
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
|
||||||
configuration_luks_tpm2_token_lib: >-
|
|
||||||
{{
|
|
||||||
'/usr/lib/x86_64-linux-gnu/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
|
|
||||||
if os_family == 'Debian'
|
|
||||||
else '/usr/lib64/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Validate LUKS UUID is available
|
- name: Validate LUKS UUID is available
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
@@ -68,13 +51,8 @@
|
|||||||
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
|
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Detect TPM2 unlock method
|
- name: Enroll TPM2 for LUKS
|
||||||
ansible.builtin.include_tasks: encryption/initramfs_detect.yml
|
when: configuration_luks_auto_method == 'tpm2'
|
||||||
|
|
||||||
- name: Enroll TPM2 via systemd-cryptenroll
|
|
||||||
when:
|
|
||||||
- configuration_luks_auto_method == 'tpm2'
|
|
||||||
- _tpm2_method | default('systemd-cryptenroll') == 'systemd-cryptenroll'
|
|
||||||
ansible.builtin.include_tasks: encryption/tpm2.yml
|
ansible.builtin.include_tasks: encryption/tpm2.yml
|
||||||
|
|
||||||
- name: Configure LUKS keyfile auto-decrypt
|
- name: Configure LUKS keyfile auto-decrypt
|
||||||
@@ -100,7 +78,7 @@
|
|||||||
}}
|
}}
|
||||||
luks_tpm2_option_list: >-
|
luks_tpm2_option_list: >-
|
||||||
{{
|
{{
|
||||||
(configuration_luks_auto_method == 'tpm2' and (_tpm2_method | default('systemd-cryptenroll')) == 'systemd-cryptenroll')
|
(configuration_luks_auto_method == 'tpm2')
|
||||||
| ternary(
|
| ternary(
|
||||||
['tpm2-device=' + configuration_luks_tpm2_device]
|
['tpm2-device=' + configuration_luks_tpm2_device]
|
||||||
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
||||||
@@ -144,16 +122,216 @@
|
|||||||
path: /mnt{{ configuration_luks_keyfile_path }}
|
path: /mnt{{ configuration_luks_keyfile_path }}
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: Configure initramfs for LUKS
|
- name: Write crypttab entry
|
||||||
ansible.builtin.include_tasks: encryption/initramfs.yml
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/crypttab
|
||||||
|
regexp: "^{{ configuration_luks_mapper_name }}\\s"
|
||||||
|
line: >-
|
||||||
|
{{ configuration_luks_mapper_name }} UUID={{ configuration_luks_uuid }}
|
||||||
|
{{ configuration_luks_crypttab_keyfile }} {{ configuration_luks_crypttab_options }}
|
||||||
|
create: true
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
- name: Configure crypttab
|
- name: Ensure keyfile pattern for initramfs-tools
|
||||||
ansible.builtin.include_tasks: encryption/crypttab.yml
|
when:
|
||||||
|
- os_family == 'Debian'
|
||||||
|
- configuration_luks_keyfile_in_use
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/cryptsetup-initramfs/conf-hook
|
||||||
|
regexp: "^KEYFILE_PATTERN="
|
||||||
|
line: "KEYFILE_PATTERN=/etc/cryptsetup-keys.d/*.key"
|
||||||
|
create: true
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Configure mkinitcpio hooks for LUKS
|
||||||
|
when: os == 'archlinux'
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/mkinitcpio.conf
|
||||||
|
regexp: "^HOOKS="
|
||||||
|
line: >-
|
||||||
|
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
|
||||||
|
block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck)
|
||||||
|
|
||||||
|
- name: Read mkinitcpio configuration
|
||||||
|
when: os == 'archlinux'
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/mkinitcpio.conf
|
||||||
|
register: configuration_mkinitcpio_slurp
|
||||||
|
|
||||||
|
- name: Build mkinitcpio FILES list
|
||||||
|
when: os == 'archlinux'
|
||||||
|
vars:
|
||||||
|
mkinitcpio_files_list: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
configuration_mkinitcpio_slurp.content | b64decode
|
||||||
|
| regex_findall('^FILES=\\(([^)]*)\\)', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
).split()
|
||||||
|
}}
|
||||||
|
mkinitcpio_files_list_new: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(mkinitcpio_files_list + [configuration_luks_keyfile_path])
|
||||||
|
if configuration_luks_keyfile_in_use
|
||||||
|
else (
|
||||||
|
mkinitcpio_files_list
|
||||||
|
| reject('equalto', configuration_luks_keyfile_path)
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_mkinitcpio_files_list_new: "{{ mkinitcpio_files_list_new }}"
|
||||||
|
|
||||||
|
- name: Configure mkinitcpio FILES list
|
||||||
|
when: os == 'archlinux'
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/mkinitcpio.conf
|
||||||
|
regexp: "^FILES="
|
||||||
|
line: >-
|
||||||
|
FILES=({{
|
||||||
|
configuration_mkinitcpio_files_list_new | join(' ')
|
||||||
|
}})
|
||||||
|
|
||||||
|
- name: Ensure dracut config directory exists
|
||||||
|
when: os_family == 'RedHat'
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /mnt/etc/dracut.conf.d
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
- name: Configure dracut for LUKS
|
- name: Configure dracut for LUKS
|
||||||
when: _initramfs_generator | default('') == 'dracut'
|
when: os_family == 'RedHat'
|
||||||
ansible.builtin.include_tasks: encryption/dracut.yml
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/dracut.conf.d/crypt.conf
|
||||||
|
content: |
|
||||||
|
add_dracutmodules+=" crypt "
|
||||||
|
{% if configuration_luks_keyfile_in_use %}
|
||||||
|
install_items+=" {{ configuration_luks_keyfile_path }} "
|
||||||
|
{% endif %}
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
- name: Configure GRUB for LUKS
|
- name: Read kernel cmdline defaults
|
||||||
when: _initramfs_generator | default('') != 'dracut' or os_family != 'RedHat'
|
when: os_family == 'RedHat'
|
||||||
ansible.builtin.include_tasks: encryption/grub.yml
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/kernel/cmdline
|
||||||
|
register: configuration_kernel_cmdline_slurp
|
||||||
|
|
||||||
|
- name: Build kernel cmdline with LUKS args
|
||||||
|
when: os_family == 'RedHat'
|
||||||
|
vars:
|
||||||
|
kernel_cmdline_current: >-
|
||||||
|
{{ configuration_kernel_cmdline_slurp.content | b64decode | trim }}
|
||||||
|
kernel_cmdline_list: >-
|
||||||
|
{{
|
||||||
|
kernel_cmdline_current.split()
|
||||||
|
if kernel_cmdline_current | length > 0 else []
|
||||||
|
}}
|
||||||
|
kernel_cmdline_filtered: >-
|
||||||
|
{{
|
||||||
|
kernel_cmdline_list
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
}}
|
||||||
|
kernel_cmdline_new: >-
|
||||||
|
{{
|
||||||
|
(kernel_cmdline_filtered + configuration_luks_kernel_args.split())
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_kernel_cmdline_new: "{{ kernel_cmdline_new }}"
|
||||||
|
|
||||||
|
- name: Write kernel cmdline with LUKS args
|
||||||
|
when: os_family == 'RedHat'
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /mnt/etc/kernel/cmdline
|
||||||
|
mode: "0644"
|
||||||
|
content: "{{ configuration_kernel_cmdline_new }}\n"
|
||||||
|
|
||||||
|
- name: Update BLS entries with LUKS kernel cmdline
|
||||||
|
when: os_family == 'RedHat'
|
||||||
|
vars:
|
||||||
|
_bls_cmdline: "{{ configuration_kernel_cmdline_new }}"
|
||||||
|
ansible.builtin.include_tasks: _bls_update.yml
|
||||||
|
|
||||||
|
- name: Read grub defaults
|
||||||
|
when: not os_family == 'RedHat'
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /mnt/etc/default/grub
|
||||||
|
register: configuration_grub_slurp
|
||||||
|
|
||||||
|
- name: Build grub command lines with LUKS args
|
||||||
|
when: not os_family == 'RedHat'
|
||||||
|
vars:
|
||||||
|
grub_content: "{{ configuration_grub_slurp.content | b64decode }}"
|
||||||
|
grub_cmdline_linux: >-
|
||||||
|
{{
|
||||||
|
grub_content
|
||||||
|
| regex_findall('^GRUB_CMDLINE_LINUX=\"(.*)\"', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
grub_cmdline_default: >-
|
||||||
|
{{
|
||||||
|
grub_content
|
||||||
|
| regex_findall('^GRUB_CMDLINE_LINUX_DEFAULT=\"(.*)\"', multiline=True)
|
||||||
|
| default([])
|
||||||
|
| first
|
||||||
|
| default('')
|
||||||
|
}}
|
||||||
|
grub_cmdline_linux_list: >-
|
||||||
|
{{
|
||||||
|
grub_cmdline_linux.split()
|
||||||
|
if grub_cmdline_linux | length > 0 else []
|
||||||
|
}}
|
||||||
|
grub_cmdline_default_list: >-
|
||||||
|
{{
|
||||||
|
grub_cmdline_default.split()
|
||||||
|
if grub_cmdline_default | length > 0 else []
|
||||||
|
}}
|
||||||
|
luks_kernel_args_list: "{{ configuration_luks_kernel_args.split() }}"
|
||||||
|
grub_cmdline_linux_new: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(
|
||||||
|
grub_cmdline_linux_list
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
+ luks_kernel_args_list
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
grub_cmdline_default_new: >-
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(
|
||||||
|
grub_cmdline_default_list
|
||||||
|
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
|
||||||
|
| list
|
||||||
|
)
|
||||||
|
+ luks_kernel_args_list
|
||||||
|
)
|
||||||
|
| unique
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_grub_content: "{{ grub_content }}"
|
||||||
|
configuration_grub_cmdline_linux: "{{ grub_cmdline_linux }}"
|
||||||
|
configuration_grub_cmdline_default: "{{ grub_cmdline_default }}"
|
||||||
|
configuration_grub_cmdline_linux_new: "{{ grub_cmdline_linux_new }}"
|
||||||
|
configuration_grub_cmdline_default_new: "{{ grub_cmdline_default_new }}"
|
||||||
|
|
||||||
|
- name: Update GRUB_CMDLINE_LINUX_DEFAULT for LUKS
|
||||||
|
when: not os_family == 'RedHat'
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /mnt/etc/default/grub
|
||||||
|
regexp: "^GRUB_CMDLINE_LINUX_DEFAULT="
|
||||||
|
line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ configuration_grub_cmdline_default_new }}"'
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Write crypttab entry
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /mnt/etc/crypttab
|
|
||||||
regexp: "^{{ configuration_luks_mapper_name }}\\s"
|
|
||||||
line: >-
|
|
||||||
{{ configuration_luks_mapper_name }} UUID={{ configuration_luks_uuid }}
|
|
||||||
{{ configuration_luks_crypttab_keyfile }} {{ configuration_luks_crypttab_options }}
|
|
||||||
create: true
|
|
||||||
mode: "0600"
|
|
||||||
@@ -1,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 }}"
|
|
||||||
@@ -86,6 +86,7 @@
|
|||||||
device: "{{ configuration_luks_device }}"
|
device: "{{ configuration_luks_device }}"
|
||||||
passphrase: "{{ configuration_luks_passphrase }}"
|
passphrase: "{{ configuration_luks_passphrase }}"
|
||||||
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
|
||||||
|
register: configuration_luks_addkey_retry
|
||||||
failed_when: false
|
failed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,26 @@
|
|||||||
---
|
---
|
||||||
# TPM2 enrollment via systemd-cryptenroll.
|
|
||||||
# Works with dracut and mkinitcpio (sd-encrypt). The user-set passphrase
|
|
||||||
# remains as a backup unlock method — no auto-generated keyfiles.
|
|
||||||
- name: Enroll TPM2 for LUKS
|
- name: Enroll TPM2 for LUKS
|
||||||
block:
|
block:
|
||||||
|
# Tempfile in chroot /tmp — accessible by both chroot and host commands
|
||||||
- name: Create temporary passphrase file for TPM2 enrollment
|
- name: Create temporary passphrase file for TPM2 enrollment
|
||||||
ansible.builtin.tempfile:
|
ansible.builtin.tempfile:
|
||||||
path: /mnt/root
|
path: /mnt/tmp
|
||||||
prefix: luks-passphrase-
|
prefix: luks-passphrase-
|
||||||
state: file
|
state: file
|
||||||
register: _tpm2_passphrase_tempfile
|
register: configuration_luks_tpm2_passphrase_tempfile
|
||||||
|
|
||||||
- name: Write passphrase into temporary file
|
- name: Write passphrase into temporary file for TPM2 enrollment
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: "{{ _tpm2_passphrase_tempfile.path }}"
|
dest: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
|
||||||
content: "{{ configuration_luks_passphrase }}"
|
content: "{{ configuration_luks_passphrase }}"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Ensure TPM device is accessible in chroot
|
- name: Enroll TPM2 token
|
||||||
ansible.builtin.shell: >-
|
|
||||||
ls /mnt/dev/tpmrm0 2>/dev/null
|
|
||||||
|| (ls /dev/tpmrm0 && cp -a /dev/tpmrm0 /mnt/dev/tpmrm0)
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Enroll TPM2 token via systemd-cryptenroll
|
|
||||||
vars:
|
vars:
|
||||||
_enroll_args: >-
|
configuration_luks_enroll_args: >-
|
||||||
{{
|
{{
|
||||||
[
|
[
|
||||||
'/usr/bin/systemd-cryptenroll',
|
'/usr/bin/systemd-cryptenroll',
|
||||||
@@ -37,28 +28,70 @@
|
|||||||
'--tpm2-with-pin=false',
|
'--tpm2-with-pin=false',
|
||||||
'--wipe-slot=tpm2',
|
'--wipe-slot=tpm2',
|
||||||
'--unlock-key-file=' + (
|
'--unlock-key-file=' + (
|
||||||
_tpm2_passphrase_tempfile.path | regex_replace('^/mnt', '')
|
configuration_luks_tpm2_passphrase_tempfile.path
|
||||||
|
| regex_replace('^/mnt', '')
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
||||||
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
||||||
+ [configuration_luks_device]
|
+ [configuration_luks_device]
|
||||||
}}
|
}}
|
||||||
ansible.builtin.command: "{{ chroot_command }} {{ _enroll_args | join(' ') }}"
|
configuration_luks_enroll_chroot_cmd: >-
|
||||||
register: _tpm2_enroll_result
|
{{ chroot_command }} {{ configuration_luks_enroll_args | join(' ') }}
|
||||||
changed_when: _tpm2_enroll_result.rc == 0
|
ansible.builtin.command: "{{ configuration_luks_enroll_chroot_cmd }}"
|
||||||
|
register: configuration_luks_tpm2_enroll_chroot
|
||||||
|
changed_when: configuration_luks_tpm2_enroll_chroot.rc == 0
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Retry TPM2 enrollment in installer environment
|
||||||
|
when:
|
||||||
|
- (configuration_luks_tpm2_enroll_chroot.rc | default(1)) != 0
|
||||||
|
vars:
|
||||||
|
configuration_luks_enroll_args: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
'/usr/bin/systemd-cryptenroll',
|
||||||
|
'--tpm2-device=' + configuration_luks_tpm2_device,
|
||||||
|
'--tpm2-with-pin=false',
|
||||||
|
'--wipe-slot=tpm2',
|
||||||
|
'--unlock-key-file=' + configuration_luks_tpm2_passphrase_tempfile.path
|
||||||
|
]
|
||||||
|
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
|
||||||
|
if configuration_luks_tpm2_pcrs | length > 0 else [])
|
||||||
|
+ [configuration_luks_device]
|
||||||
|
}}
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ configuration_luks_enroll_args }}"
|
||||||
|
register: configuration_luks_tpm2_enroll_host
|
||||||
|
changed_when: configuration_luks_tpm2_enroll_host.rc == 0
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Validate TPM2 enrollment succeeded
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- >-
|
||||||
|
(configuration_luks_tpm2_enroll_chroot.rc | default(1)) == 0
|
||||||
|
or (configuration_luks_tpm2_enroll_host.rc | default(1)) == 0
|
||||||
|
fail_msg: >-
|
||||||
|
TPM2 enrollment failed.
|
||||||
|
chroot rc={{ configuration_luks_tpm2_enroll_chroot.rc | default('n/a') }},
|
||||||
|
host rc={{ configuration_luks_tpm2_enroll_host.rc | default('n/a') }},
|
||||||
|
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
|
||||||
|
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
|
||||||
rescue:
|
rescue:
|
||||||
- name: TPM2 enrollment failed
|
- name: Warn about TPM2 enrollment failure
|
||||||
ansible.builtin.debug:
|
ansible.builtin.fail:
|
||||||
msg: >-
|
msg: >-
|
||||||
TPM2 enrollment failed: {{ _tpm2_enroll_result.stderr | default('unknown') }}.
|
WARNING: TPM2 enrollment failed — falling back to keyfile auto-decrypt.
|
||||||
The system will require the passphrase for LUKS unlock on boot.
|
The system will use a keyfile instead of TPM2 for automatic LUKS unlock.
|
||||||
TPM2 can be enrolled post-deployment via: systemd-cryptenroll --tpm2-device=auto {{ configuration_luks_device }}
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Fallback to keyfile auto-decrypt
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
configuration_luks_auto_method: keyfile
|
||||||
always:
|
always:
|
||||||
- name: Remove temporary passphrase file
|
- name: Remove TPM2 enrollment passphrase file
|
||||||
when: _tpm2_passphrase_tempfile.path is defined
|
when: configuration_luks_tpm2_passphrase_tempfile.path is defined
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ _tpm2_passphrase_tempfile.path }}"
|
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
set smartindent
|
set smartindent
|
||||||
set mouse=a
|
set mouse=a
|
||||||
insertafter: EOF
|
insertafter: EOF
|
||||||
marker: "\" {mark} CUSTOM VIM CONFIG"
|
marker: "# {mark} CUSTOM VIM CONFIG"
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
# Tuned for VM workloads: low swappiness, aggressive writeback, large page-cluster
|
# Tuned for VM workloads: low swappiness, aggressive writeback, large page-cluster
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
- file: encryption.yml
|
- file: encryption.yml
|
||||||
when: "{{ system_cfg.luks.enabled | bool }}"
|
when: "{{ system_cfg.luks.enabled | bool }}"
|
||||||
- file: bootloader.yml
|
- file: bootloader.yml
|
||||||
- file: secure_boot.yml
|
|
||||||
when: "{{ system_cfg.features.secure_boot.enabled | bool }}"
|
|
||||||
- file: extras.yml
|
- file: extras.yml
|
||||||
- file: network.yml
|
- file: network.yml
|
||||||
- file: users.yml
|
- file: users.yml
|
||||||
|
|||||||
@@ -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'
|
|
||||||
}}
|
|
||||||
@@ -2,12 +2,6 @@
|
|||||||
- name: Enable systemd services
|
- name: Enable systemd services
|
||||||
when: _configuration_platform.init_system == 'systemd'
|
when: _configuration_platform.init_system == 'systemd'
|
||||||
vars:
|
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: >-
|
configuration_systemd_services: >-
|
||||||
{{
|
{{
|
||||||
['NetworkManager']
|
['NetworkManager']
|
||||||
@@ -15,31 +9,12 @@
|
|||||||
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
|
||||||
+ ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else [])
|
+ ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else [])
|
||||||
+ (['logrotate', 'systemd-timesyncd'] if os == 'archlinux' 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 }}"
|
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}"
|
||||||
loop: "{{ configuration_systemd_services }}"
|
loop: "{{ configuration_systemd_services }}"
|
||||||
register: configuration_enable_service_result
|
register: configuration_enable_service_result
|
||||||
changed_when: configuration_enable_service_result.rc == 0
|
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
|
- name: Enable OpenRC services
|
||||||
when: _configuration_platform.init_system == 'openrc'
|
when: _configuration_platform.init_system == 'openrc'
|
||||||
vars:
|
vars:
|
||||||
|
|||||||
@@ -15,15 +15,15 @@
|
|||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
|
|
||||||
- name: Deploy per-user sudoers rules
|
- name: Deploy per-user sudoers rules
|
||||||
when: item.value.sudo is defined and (item.value.sudo | string | length > 0)
|
when: item.sudo | default(false)
|
||||||
vars:
|
vars:
|
||||||
configuration_sudoers_rule: >-
|
configuration_sudoers_rule: >-
|
||||||
{{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
{{ item.sudo if item.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }}
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "{{ item.key }} {{ configuration_sudoers_rule }}\n"
|
content: "{{ item.name }} {{ configuration_sudoers_rule }}\n"
|
||||||
dest: "/mnt/etc/sudoers.d/{{ item.key }}"
|
dest: "/mnt/etc/sudoers.d/{{ item.name }}"
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
loop: "{{ system_cfg.users }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.key }}"
|
label: "{{ item.name }}"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Set root password
|
- name: Set root password
|
||||||
when: (system_cfg.root.password | default('') | string | length) > 0
|
|
||||||
ansible.builtin.shell: >-
|
ansible.builtin.shell: >-
|
||||||
set -o pipefail &&
|
set -o pipefail &&
|
||||||
echo 'root:{{ system_cfg.root.password | password_hash("sha512") }}' | {{ chroot_command }} /usr/sbin/chpasswd -e
|
echo 'root:{{ system_cfg.root.password | password_hash("sha512") }}' | {{ chroot_command }} /usr/sbin/chpasswd -e
|
||||||
@@ -10,13 +9,6 @@
|
|||||||
changed_when: configuration_root_result.rc == 0
|
changed_when: configuration_root_result.rc == 0
|
||||||
no_log: true
|
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
|
- name: Set root shell
|
||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
{{ chroot_command }} /usr/sbin/usermod --shell {{ system_cfg.root.shell }} root
|
{{ chroot_command }} /usr/sbin/usermod --shell {{ system_cfg.root.shell }} root
|
||||||
@@ -26,43 +18,44 @@
|
|||||||
- name: Create user accounts
|
- name: Create user accounts
|
||||||
vars:
|
vars:
|
||||||
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
configuration_user_group: "{{ _configuration_platform.user_group }}"
|
||||||
|
# UID starts at 1000; safe for fresh installs only
|
||||||
configuration_useradd_cmd: >-
|
configuration_useradd_cmd: >-
|
||||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||||
--uid {{ 1000 + _idx }}
|
--uid {{ 1000 + ansible_loop.index0 }}
|
||||||
--groups {{ configuration_user_group }} {{ item.key }}
|
--groups {{ configuration_user_group }} {{ item.name }}
|
||||||
{{ ('--password ' ~ (item.value.password | password_hash('sha512'))) if (item.value.password | default('') | string | length > 0) else '' }}
|
--password {{ item.password | password_hash('sha512') }} --shell {{ item.shell | default('/bin/bash') }}
|
||||||
--shell {{ item.value.shell | default('/bin/bash') }}
|
|
||||||
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
loop: "{{ system_cfg.users }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
index_var: _idx
|
extended: true
|
||||||
label: "{{ item.key }}"
|
label: "{{ item.name }}"
|
||||||
register: configuration_user_result
|
register: configuration_user_result
|
||||||
changed_when: configuration_user_result.rc == 0
|
changed_when: configuration_user_result.rc == 0
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Ensure .ssh directory exists
|
- name: Ensure .ssh directory exists
|
||||||
when: (item.value['keys'] | default([]) | length) > 0
|
when: item['keys'] | default([]) | length > 0
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "/mnt/home/{{ item.key }}/.ssh"
|
path: "/mnt/home/{{ item.name }}/.ssh"
|
||||||
state: directory
|
state: directory
|
||||||
owner: "{{ 1000 + _idx }}"
|
owner: "{{ 1000 + ansible_loop.index0 }}"
|
||||||
group: "{{ 1000 + _idx }}"
|
group: "{{ 1000 + ansible_loop.index0 }}"
|
||||||
mode: "0700"
|
mode: "0700"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
loop: "{{ system_cfg.users }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
index_var: _idx
|
extended: true
|
||||||
label: "{{ item.key }}"
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
- name: Deploy SSH authorized_keys
|
- name: Add SSH public keys to authorized_keys
|
||||||
when: (item.value['keys'] | default([]) | length) > 0
|
vars:
|
||||||
ansible.builtin.copy:
|
configuration_uid: "{{ 1000 + (system_cfg.users | map(attribute='name') | list).index(item.0.name) }}"
|
||||||
content: "{{ item.value['keys'] | join('\n') }}\n"
|
ansible.builtin.lineinfile:
|
||||||
dest: "/mnt/home/{{ item.key }}/.ssh/authorized_keys"
|
path: "/mnt/home/{{ item.0.name }}/.ssh/authorized_keys"
|
||||||
owner: "{{ 1000 + _idx }}"
|
line: "{{ item.1 }}"
|
||||||
group: "{{ 1000 + _idx }}"
|
owner: "{{ configuration_uid }}"
|
||||||
|
group: "{{ configuration_uid }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
loop: "{{ system_cfg.users | dict2items }}"
|
create: true
|
||||||
|
loop: "{{ system_cfg.users | subelements('keys', skip_missing=True) }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
index_var: _idx
|
label: "{{ item.0.name }}: {{ item.1[:40] }}..."
|
||||||
label: "{{ item.key }}"
|
|
||||||
|
|||||||
@@ -65,15 +65,3 @@ configuration_platform_config:
|
|||||||
grub_mkconfig_prefix: grub-mkconfig
|
grub_mkconfig_prefix: grub-mkconfig
|
||||||
locale_gen: false
|
locale_gen: false
|
||||||
init_system: runit
|
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
|
|
||||||
|
|||||||
@@ -87,10 +87,9 @@
|
|||||||
- name: Switch to SSH connection
|
- name: Switch to SSH connection
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_connection: ssh
|
ansible_connection: ssh
|
||||||
ansible_host: "{{ system_cfg.network.ip }}"
|
|
||||||
ansible_port: 22
|
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
ansible_password: ""
|
ansible_password: ""
|
||||||
|
ansible_host: "{{ system_cfg.network.ip }}"
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
|
|
||||||
- name: Reset connection for SSH switchover
|
- name: Reset connection for SSH switchover
|
||||||
|
|||||||
@@ -68,23 +68,6 @@
|
|||||||
Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry.
|
Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry.
|
||||||
quiet: true
|
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
|
- name: Abort if the host is not booted from the Arch install media
|
||||||
when:
|
when:
|
||||||
- not (custom_iso | bool)
|
- not (custom_iso | bool)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
state: latest
|
state: latest
|
||||||
loop:
|
loop:
|
||||||
- { name: glibc }
|
- { name: glibc }
|
||||||
- { name: lua, os: [almalinux, fedora, rhel, rocky] }
|
|
||||||
- { name: dnf, os: [almalinux, fedora, rhel, rocky] }
|
- { name: dnf, os: [almalinux, fedora, rhel, rocky] }
|
||||||
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
|
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
|
||||||
- { name: debian-archive-keyring, os: [debian] }
|
- { name: debian-archive-keyring, os: [debian] }
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ system_defaults:
|
|||||||
mirror: ""
|
mirror: ""
|
||||||
packages: []
|
packages: []
|
||||||
disks: []
|
disks: []
|
||||||
users: {}
|
users: []
|
||||||
root:
|
root:
|
||||||
password: ""
|
password: ""
|
||||||
shell: "/bin/bash"
|
shell: "/bin/bash"
|
||||||
@@ -129,21 +129,8 @@ system_defaults:
|
|||||||
rhel_repo:
|
rhel_repo:
|
||||||
source: "iso" # iso|satellite|none — how RHEL systems get packages post-install
|
source: "iso" # iso|satellite|none — how RHEL systems get packages post-install
|
||||||
url: "" # Satellite/custom repo URL when source=satellite
|
url: "" # Satellite/custom repo URL when source=satellite
|
||||||
aur:
|
|
||||||
enabled: false
|
|
||||||
helper: "yay" # yay|paru
|
|
||||||
user: "_aur_builder"
|
|
||||||
chroot:
|
chroot:
|
||||||
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn
|
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.
|
# Per-hypervisor required fields — drives data-driven validation.
|
||||||
# All virtual types additionally require network bridge or interfaces.
|
# All virtual types additionally require network bridge or interfaces.
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
}}
|
}}
|
||||||
# --- Storage & accounts ---
|
# --- Storage & accounts ---
|
||||||
disks: "{{ system_raw.disks | default([]) }}"
|
disks: "{{ system_raw.disks | default([]) }}"
|
||||||
users: "{{ system_raw.users | default({}) }}"
|
users: "{{ system_raw.users | default([]) }}"
|
||||||
root:
|
root:
|
||||||
password: "{{ system_raw.root.password | string }}"
|
password: "{{ system_raw.root.password | string }}"
|
||||||
shell: "{{ system_raw.root.shell | default('/bin/bash') | string }}"
|
shell: "{{ system_raw.root.shell | default('/bin/bash') | string }}"
|
||||||
@@ -144,16 +144,27 @@
|
|||||||
url: "{{ system_raw.features.rhel_repo.url | default('') | string }}"
|
url: "{{ system_raw.features.rhel_repo.url | default('') | string }}"
|
||||||
chroot:
|
chroot:
|
||||||
tool: "{{ system_raw.features.chroot.tool | string }}"
|
tool: "{{ system_raw.features.chroot.tool | string }}"
|
||||||
initramfs:
|
|
||||||
generator: "{{ system_raw.features.initramfs.generator | default('') | string | lower }}"
|
|
||||||
desktop:
|
|
||||||
enabled: "{{ system_raw.features.desktop.enabled | bool }}"
|
|
||||||
environment: "{{ system_raw.features.desktop.environment | default('') | string | lower }}"
|
|
||||||
display_manager: "{{ system_raw.features.desktop.display_manager | default('') | string | lower }}"
|
|
||||||
secure_boot:
|
|
||||||
enabled: "{{ system_raw.features.secure_boot.enabled | bool }}"
|
|
||||||
method: "{{ system_raw.features.secure_boot.method | default('') | string | lower }}"
|
|
||||||
hostname: "{{ system_name }}"
|
hostname: "{{ system_name }}"
|
||||||
os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}"
|
os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}"
|
||||||
os_version: "{{ system_raw.version | default('') | string }}"
|
os_version: "{{ system_raw.version | default('') | string }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
- name: Populate primary network fields from first interface
|
||||||
|
when:
|
||||||
|
- system_cfg.network.interfaces | length > 0
|
||||||
|
- system_cfg.network.bridge | default('') | string | length == 0
|
||||||
|
vars:
|
||||||
|
_primary: "{{ system_cfg.network.interfaces[0] }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
system_cfg: >-
|
||||||
|
{{
|
||||||
|
system_cfg | combine({
|
||||||
|
'network': system_cfg.network | combine({
|
||||||
|
'bridge': _primary.bridge | default(''),
|
||||||
|
'vlan': _primary.vlan | default(''),
|
||||||
|
'ip': _primary.ip | default(''),
|
||||||
|
'prefix': _primary.prefix | default(''),
|
||||||
|
'gateway': _primary.gateway | default('')
|
||||||
|
})
|
||||||
|
}, recursive=True)
|
||||||
|
}}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
that:
|
that:
|
||||||
- system is mapping
|
- system is mapping
|
||||||
- system.network is not defined or system.network is mapping
|
- system.network is not defined or system.network is mapping
|
||||||
- system.users is not defined or system.users is mapping
|
- system.users is not defined or (system.users is iterable and system.users is not string and system.users is not mapping)
|
||||||
- system.root is not defined or system.root is mapping
|
- system.root is not defined or system.root is mapping
|
||||||
- system.luks is not defined or system.luks is mapping
|
- system.luks is not defined or system.luks is mapping
|
||||||
- system.features is not defined or system.features is mapping
|
- system.features is not defined or system.features is mapping
|
||||||
fail_msg: "system and its nested keys (network, root, luks, features, users) must be dictionaries."
|
fail_msg: "system and its nested keys (network, root, luks, features) must be dictionaries; system.users must be a list."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate DNS lists (not strings)
|
- name: Validate DNS lists (not strings)
|
||||||
@@ -25,17 +25,17 @@
|
|||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate system.users entries
|
- name: Validate system.users entries
|
||||||
when: system.users is defined and system.users is mapping and system.users | length > 0
|
when: system.users is defined and system.users | length > 0
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- item.value is mapping
|
- item is mapping
|
||||||
- item.key | string | length > 0
|
- item.name is defined and (item.name | string | length) > 0
|
||||||
- item.value['keys'] is not defined or (item.value['keys'] is iterable and item.value['keys'] is not string)
|
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
|
||||||
fail_msg: "Each system.users entry must be a dict keyed by username; 'keys' must be a list."
|
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
|
||||||
quiet: true
|
quiet: true
|
||||||
loop: "{{ system.users | dict2items }}"
|
loop: "{{ system.users }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.key }}"
|
label: "{{ item.name | default('(unnamed)') }}"
|
||||||
|
|
||||||
- name: Validate system features input types
|
- name: Validate system features input types
|
||||||
when: system.features is defined
|
when: system.features is defined
|
||||||
|
|||||||
@@ -81,14 +81,10 @@
|
|||||||
when:
|
when:
|
||||||
- system_cfg.type == "virtual"
|
- system_cfg.type == "virtual"
|
||||||
- hypervisor_type != "vmware"
|
- hypervisor_type != "vmware"
|
||||||
vars:
|
|
||||||
_primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}"
|
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_host: "{{ system_cfg.network.ip }}"
|
ansible_user: "{{ system_cfg.users[0].name }}"
|
||||||
ansible_port: 22
|
ansible_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_user: "{{ _primary.key }}"
|
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_become_password: "{{ _primary.value.password }}"
|
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
@@ -96,12 +92,12 @@
|
|||||||
when: hypervisor_type == "vmware"
|
when: hypervisor_type == "vmware"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_connection: vmware_tools
|
ansible_connection: vmware_tools
|
||||||
ansible_host: "{{ hypervisor_cfg.url }}"
|
ansible_vmware_host: "{{ hypervisor_cfg.url }}"
|
||||||
ansible_port: 443
|
ansible_vmware_port: 443
|
||||||
ansible_user: root
|
|
||||||
ansible_password: ""
|
|
||||||
ansible_vmware_user: "{{ hypervisor_cfg.username }}"
|
ansible_vmware_user: "{{ hypervisor_cfg.username }}"
|
||||||
ansible_vmware_password: "{{ hypervisor_cfg.password }}"
|
ansible_vmware_password: "{{ hypervisor_cfg.password }}"
|
||||||
ansible_vmware_guest_path: "/{{ hypervisor_cfg.datacenter }}/vm{{ system_cfg.path }}/{{ hostname }}"
|
ansible_vmware_guest_path: "/{{ hypervisor_cfg.datacenter }}/vm{{ system_cfg.path }}/{{ hostname }}"
|
||||||
ansible_vmware_validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
ansible_vmware_validate_certs: "{{ hypervisor_cfg.certs | bool }}"
|
||||||
|
ansible_vmware_tools_user: root
|
||||||
|
ansible_vmware_tools_password: "{{ system_cfg.root.password }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|||||||
@@ -17,27 +17,6 @@
|
|||||||
- name: Normalize disk configuration
|
- name: Normalize disk configuration
|
||||||
ansible.builtin.include_tasks: _normalize_disks.yml
|
ansible.builtin.include_tasks: _normalize_disks.yml
|
||||||
|
|
||||||
- name: Populate primary network fields from first interface
|
|
||||||
when:
|
|
||||||
- system_cfg is defined
|
|
||||||
- system_cfg.network.interfaces | default([]) | length > 0
|
|
||||||
- system_cfg.network.ip | default('') | string | length == 0
|
|
||||||
vars:
|
|
||||||
_primary: "{{ system_cfg.network.interfaces[0] }}"
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
system_cfg: >-
|
|
||||||
{{
|
|
||||||
system_cfg | combine({
|
|
||||||
'network': system_cfg.network | combine({
|
|
||||||
'bridge': _primary.bridge | default(''),
|
|
||||||
'vlan': _primary.vlan | default(''),
|
|
||||||
'ip': _primary.ip | default(''),
|
|
||||||
'prefix': _primary.prefix | default(''),
|
|
||||||
'gateway': _primary.gateway | default('')
|
|
||||||
})
|
|
||||||
}, recursive=True)
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Check if pre-computed system_cfg needs enrichment
|
- name: Check if pre-computed system_cfg needs enrichment
|
||||||
when: system_cfg is defined
|
when: system_cfg is defined
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|||||||
@@ -261,16 +261,13 @@
|
|||||||
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
|
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate at least one user with a password is defined
|
- name: Validate at least one user is defined
|
||||||
vars:
|
|
||||||
_pw_users: "{{ system_cfg.users | dict2items | selectattr('value.password', 'defined') | list }}"
|
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- system_cfg.users | default({}) | length > 0
|
- system_cfg.users | default([]) | length > 0
|
||||||
- _pw_users | length > 0
|
- system_cfg.users[0].name is defined and (system_cfg.users[0].name | string | length) > 0
|
||||||
- _pw_users[0].key | string | length > 0
|
- system_cfg.users[0].password is defined and (system_cfg.users[0].password | string | length) > 0
|
||||||
- _pw_users[0].value.password | string | length > 0
|
fail_msg: "At least one user with a name and password must be defined in system.users[]."
|
||||||
fail_msg: "At least one user with a password must be defined in system.users."
|
|
||||||
quiet: true
|
quiet: true
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
ansible.builtin.command: >-
|
ansible.builtin.command: >-
|
||||||
tune2fs -O "^orphan_file,^metadata_csum_seed"
|
tune2fs -O "^orphan_file,^metadata_csum_seed"
|
||||||
"{{ install_drive }}{{ partitioning_part_sep }}{{ partitioning_boot_fs_partition_suffix }}"
|
"{{ install_drive }}{{ partitioning_part_sep }}{{ partitioning_boot_fs_partition_suffix }}"
|
||||||
|
register: partitioning_boot_ext4_tune_result
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Create swap filesystem
|
- name: Create swap filesystem
|
||||||
|
|||||||
@@ -65,7 +65,9 @@
|
|||||||
ansible.builtin.command: "{{ item }}"
|
ansible.builtin.command: "{{ item }}"
|
||||||
loop:
|
loop:
|
||||||
- "partprobe {{ install_drive }}"
|
- "partprobe {{ install_drive }}"
|
||||||
|
- "blockdev --rereadpt {{ install_drive }}"
|
||||||
- "udevadm settle"
|
- "udevadm settle"
|
||||||
|
register: partitioning_partprobe_result
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
@@ -89,7 +91,9 @@
|
|||||||
ansible.builtin.command: "{{ item }}"
|
ansible.builtin.command: "{{ item }}"
|
||||||
loop:
|
loop:
|
||||||
- "partprobe {{ install_drive }}"
|
- "partprobe {{ install_drive }}"
|
||||||
|
- "blockdev --rereadpt {{ install_drive }}"
|
||||||
- "udevadm settle"
|
- "udevadm settle"
|
||||||
|
register: partitioning_partprobe_retry
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
@@ -112,5 +116,6 @@
|
|||||||
loop:
|
loop:
|
||||||
- "partprobe {{ install_drive }}"
|
- "partprobe {{ install_drive }}"
|
||||||
- "udevadm settle"
|
- "udevadm settle"
|
||||||
|
register: partitioning_partprobe_settle
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
- >-
|
- >-
|
||||||
system_cfg.features.cis.enabled | bool or (
|
system_cfg.features.cis.enabled | bool or (
|
||||||
not (system_cfg.features.cis.enabled | bool) and (
|
not (system_cfg.features.cis.enabled | bool) and (
|
||||||
(system_cfg.filesystem == 'btrfs' and item.path in ['/home', '/var/log']
|
(system_cfg.filesystem == 'btrfs' and item.path in ['/home', '/var/log', '/var/cache/pacman/pkg'])
|
||||||
+ (['/var/cache/pacman/pkg'] if os == 'archlinux' else []))
|
|
||||||
or (item.path not in ['/home', '/var', '/var/log', '/var/log/audit', '/var/cache/pacman/pkg'])
|
or (item.path not in ['/home', '/var', '/var/log', '/var/log/audit', '/var/cache/pacman/pkg'])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
- >-
|
- >-
|
||||||
not (item.path in ['/swap', '/var/cache/pacman/pkg'] and (system_cfg.filesystem != 'btrfs' or os != 'archlinux'))
|
not (item.path in ['/swap', '/var/cache/pacman/pkg'] and system_cfg.filesystem != 'btrfs')
|
||||||
- system_cfg.features.swap.enabled | bool or item.path != '/swap'
|
- system_cfg.features.swap.enabled | bool or item.path != '/swap'
|
||||||
ansible.posix.mount:
|
ansible.posix.mount:
|
||||||
path: /mnt{{ item.path }}
|
path: /mnt{{ item.path }}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
algorithm: "{{ system_cfg.luks.pbkdf }}"
|
algorithm: "{{ system_cfg.luks.pbkdf }}"
|
||||||
iteration_time: "{{ (system_cfg.luks.iter | float) / 1000 }}"
|
iteration_time: "{{ (system_cfg.luks.iter | float) / 1000 }}"
|
||||||
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
||||||
|
register: partitioning_luks_format_result
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Force-close LUKS mapper
|
- name: Force-close LUKS mapper
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
name: "{{ system_cfg.luks.mapper }}"
|
name: "{{ system_cfg.luks.mapper }}"
|
||||||
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
||||||
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
|
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
|
||||||
|
register: partitioning_luks_open_result
|
||||||
no_log: true
|
no_log: true
|
||||||
rescue:
|
rescue:
|
||||||
- name: Force-close stale LUKS mapper
|
- name: Force-close stale LUKS mapper
|
||||||
@@ -77,6 +79,7 @@
|
|||||||
name: "{{ system_cfg.luks.mapper }}"
|
name: "{{ system_cfg.luks.mapper }}"
|
||||||
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
passphrase: "{{ system_cfg.luks.passphrase | string }}"
|
||||||
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
|
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
|
||||||
|
register: partitioning_luks_open_retry
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Get LUKS UUID
|
- name: Get LUKS UUID
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
when:
|
when:
|
||||||
- system_cfg.features.cis.enabled | bool or item.subvol not in ['var_log_audit']
|
- system_cfg.features.cis.enabled | bool or item.subvol not in ['var_log_audit']
|
||||||
- system_cfg.features.swap.enabled | bool or item.subvol != 'swap'
|
- system_cfg.features.swap.enabled | bool or item.subvol != 'swap'
|
||||||
- item.os is not defined or os in item.os
|
|
||||||
ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
|
ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
|
||||||
args:
|
args:
|
||||||
creates: /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
|
creates: /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
|
||||||
@@ -52,18 +51,12 @@
|
|||||||
- { subvol: swap }
|
- { subvol: swap }
|
||||||
- { subvol: home }
|
- { subvol: home }
|
||||||
- { subvol: var }
|
- { subvol: var }
|
||||||
- { subvol: pkg, os: [archlinux] }
|
- { subvol: pkg }
|
||||||
- { subvol: var_log }
|
- { subvol: var_log }
|
||||||
- { subvol: var_log_audit }
|
- { subvol: var_log_audit }
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.subvol }}"
|
label: "{{ item.subvol }}"
|
||||||
|
register: partitioning_btrfs_subvol_result
|
||||||
- name: Set default btrfs subvolume to @
|
|
||||||
ansible.builtin.shell: >-
|
|
||||||
btrfs subvolume list /mnt | awk '/ path @$/ {print $2}'
|
|
||||||
| xargs -I{} btrfs subvolume set-default {} /mnt
|
|
||||||
register: partitioning_btrfs_default_result
|
|
||||||
changed_when: partitioning_btrfs_default_result.rc == 0
|
|
||||||
|
|
||||||
- name: Set quotas for subvolumes
|
- name: Set quotas for subvolumes
|
||||||
when: system_cfg.features.cis.enabled | bool
|
when: system_cfg.features.cis.enabled | bool
|
||||||
@@ -81,6 +74,7 @@
|
|||||||
btrfs filesystem mkswapfile --size {{ partitioning_swap_size_gb }}g --uuid clear /mnt/@swap/swapfile
|
btrfs filesystem mkswapfile --size {{ partitioning_swap_size_gb }}g --uuid clear /mnt/@swap/swapfile
|
||||||
args:
|
args:
|
||||||
creates: /mnt/@swap/swapfile
|
creates: /mnt/@swap/swapfile
|
||||||
|
register: partitioning_btrfs_swap_result
|
||||||
|
|
||||||
- name: Unmount Partition
|
- name: Unmount Partition
|
||||||
ansible.posix.mount:
|
ansible.posix.mount:
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ virtualization_libvirt_ovmf_vars: /usr/share/edk2/x64/OVMF_VARS.4m.fd
|
|||||||
|
|
||||||
virtualization_tpm2_enabled: >-
|
virtualization_tpm2_enabled: >-
|
||||||
{{
|
{{
|
||||||
(
|
(system_cfg.luks.enabled | bool)
|
||||||
(system_cfg.luks.enabled | bool)
|
and (system_cfg.luks.auto | bool)
|
||||||
and (system_cfg.luks.auto | bool)
|
and (
|
||||||
and (system_cfg.luks.method | lower == 'tpm2')
|
(system_cfg.luks.method | lower)
|
||||||
|
== 'tpm2'
|
||||||
)
|
)
|
||||||
or (system_cfg.features.secure_boot.enabled | default(false) | bool)
|
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Build Xen disk definitions
|
|
||||||
when: virtualization_xen_disks is not defined
|
|
||||||
block:
|
|
||||||
- name: Compute Xen disk configuration
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
virtualization_xen_disks: "{{ virtualization_xen_disks | default([]) + [_xen_disk_cfg] }}"
|
|
||||||
vars:
|
|
||||||
device_letter_map: "{{ disk_letter_map }}"
|
|
||||||
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
|
|
||||||
_xen_disk_cfg: >-
|
|
||||||
{{
|
|
||||||
{
|
|
||||||
'path': (
|
|
||||||
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
|
|
||||||
if ansible_loop.index0 == 0
|
|
||||||
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
|
|
||||||
),
|
|
||||||
'target': 'xvd' ~ device_letter,
|
|
||||||
'size': (item.size | float)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
loop: "{{ system_cfg.disks }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item | to_json }}"
|
|
||||||
extended: true
|
|
||||||
@@ -40,10 +40,10 @@
|
|||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Undefine libvirt VM
|
- name: Undefine libvirt VM
|
||||||
ansible.builtin.command:
|
community.libvirt.virt:
|
||||||
cmd: "virsh -c {{ libvirt_uri | default('qemu:///system') }} undefine {{ hostname }} --nvram"
|
name: "{{ hostname }}"
|
||||||
register: _libvirt_undefine_result
|
command: undefine
|
||||||
changed_when: _libvirt_undefine_result.rc == 0
|
uri: "{{ libvirt_uri | default('qemu:///system') }}"
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Remove libvirt disk images
|
- name: Remove libvirt disk images
|
||||||
@@ -70,7 +70,6 @@
|
|||||||
- xl
|
- xl
|
||||||
- destroy
|
- destroy
|
||||||
- "{{ hostname }}"
|
- "{{ hostname }}"
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
- name: Remove Xen VM config
|
- name: Remove Xen VM config
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{{ out }}
|
{{ out }}
|
||||||
community.proxmox.proxmox_kvm:
|
community.proxmox.proxmox_kvm:
|
||||||
ciuser: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).key }}"
|
ciuser: "{{ system_cfg.users[0].name }}"
|
||||||
cipassword: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first).value.password }}"
|
cipassword: "{{ system_cfg.users[0].password }}"
|
||||||
ciupgrade: false
|
ciupgrade: false
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
name: "{{ hostname }}"
|
name: "{{ hostname }}"
|
||||||
|
|||||||
@@ -2,7 +2,28 @@
|
|||||||
- name: Deploy VM on Xen
|
- name: Deploy VM on Xen
|
||||||
block:
|
block:
|
||||||
- name: Build disk definitions
|
- name: Build disk definitions
|
||||||
ansible.builtin.include_tasks: _xen_disks.yml
|
ansible.builtin.set_fact:
|
||||||
|
virtualization_xen_disks: "{{ virtualization_xen_disks | default([]) + [virtualization_xen_disk_cfg] }}"
|
||||||
|
vars:
|
||||||
|
device_letter_map: "{{ disk_letter_map }}"
|
||||||
|
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
|
||||||
|
virtualization_xen_disk_cfg: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'path': (
|
||||||
|
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
|
||||||
|
if ansible_loop.index0 == 0
|
||||||
|
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
|
||||||
|
),
|
||||||
|
'target': 'xvd' ~ device_letter,
|
||||||
|
'size': (item.size | float)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
loop: "{{ system_cfg.disks }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item | to_json }}"
|
||||||
|
extended: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Create VM disks for Xen
|
- name: Create VM disks for Xen
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -4,22 +4,17 @@ ssh_pwauth: true
|
|||||||
package_update: false
|
package_update: false
|
||||||
package_upgrade: false
|
package_upgrade: false
|
||||||
users:
|
users:
|
||||||
{% for username, attrs in system_cfg.users.items() %}
|
{% for user in system_cfg.users %}
|
||||||
- name: "{{ username }}"
|
- name: "{{ user.name }}"
|
||||||
primary_group: "{{ username }}"
|
primary_group: "{{ user.name }}"
|
||||||
groups: users
|
groups: users
|
||||||
{% if attrs.sudo | default(false) | bool %}
|
|
||||||
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
||||||
{% endif %}
|
passwd: "{{ user.password | password_hash('sha512') }}"
|
||||||
{% if attrs.password | default('') | length > 0 %}
|
|
||||||
passwd: "{{ attrs.password | password_hash('sha512') }}"
|
|
||||||
lock_passwd: false
|
lock_passwd: false
|
||||||
{% else %}
|
{% set ssh_keys = user['keys'] | default([]) %}
|
||||||
lock_passwd: true
|
{% if ssh_keys | length > 0 %}
|
||||||
{% endif %}
|
|
||||||
{% if 'keys' in attrs and attrs['keys'] is iterable and attrs['keys'] is not string and attrs['keys'] | length > 0 %}
|
|
||||||
ssh_authorized_keys:
|
ssh_authorized_keys:
|
||||||
{% for key in attrs['keys'] %}
|
{% for key in ssh_keys %}
|
||||||
- "{{ key }}"
|
- "{{ key }}"
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user