refactor(users): migrate system.user to system.users[] for multi-user support
This commit is contained in:
49
README.md
49
README.md
@@ -122,10 +122,11 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: xfs
|
fstype: xfs
|
||||||
user:
|
users:
|
||||||
name: ops
|
- name: ops
|
||||||
password: CHANGE_ME
|
password: CHANGE_ME
|
||||||
key: "ssh-ed25519 AAAA..."
|
keys:
|
||||||
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: CHANGE_ME
|
password: CHANGE_ME
|
||||||
luks:
|
luks:
|
||||||
@@ -173,32 +174,36 @@ Top-level host install/runtime settings. Use these keys under `system`.
|
|||||||
| `packages` | list | `[]` | Additional packages installed post-reboot |
|
| `packages` | list | `[]` | Additional packages installed post-reboot |
|
||||||
| `network` | dict | see below | Network configuration |
|
| `network` | dict | see below | Network configuration |
|
||||||
| `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#45-multi-disk-schema)) |
|
| `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#45-multi-disk-schema)) |
|
||||||
| `user` | dict | see below | User account settings |
|
| `users` | list | `[]` | User accounts (see below) |
|
||||||
| `root` | dict | see below | Root account settings |
|
| `root` | dict | see below | Root account settings |
|
||||||
| `luks` | dict | see below | Encryption settings |
|
| `luks` | dict | see below | Encryption settings |
|
||||||
| `features` | dict | see below | Feature toggles |
|
| `features` | dict | see below | Feature toggles |
|
||||||
|
|
||||||
#### `system.network`
|
#### `system.network`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| -------------- | ----------- | ------- | --------------------------------------------------- |
|
| -------------- | ---------- | ------- | ---------------------------------------------------- |
|
||||||
| `bridge` | string | empty | Hypervisor network/bridge name |
|
| `bridge` | string | empty | Hypervisor network/bridge name |
|
||||||
| `vlan` | string/int | empty | VLAN tag |
|
| `vlan` | string/int | empty | VLAN tag |
|
||||||
| `ip` | string | empty | Static IP (omit for DHCP) |
|
| `ip` | string | empty | Static IP (omit for DHCP) |
|
||||||
| `prefix` | int | empty | CIDR prefix for static IP |
|
| `prefix` | int | empty | CIDR prefix for static IP |
|
||||||
| `gateway` | string | empty | Default gateway (static only) |
|
| `gateway` | string | empty | Default gateway (static only) |
|
||||||
| `dns.servers` | list/string | `[]` | DNS resolvers; comma-separated string is normalized |
|
| `dns.servers` | list | `[]` | DNS resolvers (must be a YAML list) |
|
||||||
| `dns.search` | list/string | `[]` | Search domains; comma-separated string is normalized |
|
| `dns.search` | list | `[]` | Search domains (must be a YAML list) |
|
||||||
|
| `interfaces` | list | `[]` | Multi-NIC config (overrides flat fields above) |
|
||||||
|
|
||||||
#### `system.user`
|
When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway`, `vlan`) are auto-wrapped into a single-entry `interfaces[]` list. When `interfaces` is set, it takes precedence and the flat fields are back-populated from `interfaces[0]` for backward compatibility. Each `interfaces[]` entry supports: `name`, `bridge` (required), `vlan`, `ip`, `prefix`, `gateway`.
|
||||||
|
|
||||||
Credentials are prompted interactively by default via `vars_prompt` in `main.yml`, but can be supplied via inventory, vars files, or `-e` for non-interactive runs.
|
#### `system.users`
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
A list of user account dictionaries. Credentials for the first user are prompted interactively by default via `vars_prompt` in `main.yml`, but can be supplied via inventory, vars files, or `-e` for non-interactive runs.
|
||||||
| ---------- | ------ | ------- | ------------------------------------- |
|
|
||||||
| `name` | string | empty | Username created on target |
|
| Key | Type | Default | Description |
|
||||||
| `password` | string | empty | User password (also used for sudo) |
|
| ---------- | ------ | ------- | -------------------------------------------- |
|
||||||
| `key` | string | empty | SSH public key for `authorized_keys` |
|
| `name` | string | empty | Username created on target (required) |
|
||||||
|
| `password` | string | empty | User password (also used for sudo) |
|
||||||
|
| `keys` | list | `[]` | SSH public keys for `authorized_keys` |
|
||||||
|
| `sudo` | string | empty | Custom sudoers rule (optional, per-user) |
|
||||||
|
|
||||||
#### `system.root`
|
#### `system.root`
|
||||||
|
|
||||||
@@ -387,7 +392,7 @@ To protect sensitive information such as passwords, API keys, and other confiden
|
|||||||
|
|
||||||
- For virtual installs, `system.cpus`, `system.memory`, and `system.disks[0].size` are required and validated.
|
- For virtual installs, `system.cpus`, `system.memory`, and `system.disks[0].size` are required and validated.
|
||||||
- For physical installs, sizing is derived from the detected install drive; set installer access (`ansible_user`/`ansible_password`) when the installer environment differs from the prompted user credentials.
|
- For physical installs, sizing is derived from the detected install drive; set installer access (`ansible_user`/`ansible_password`) when the installer environment differs from the prompted user credentials.
|
||||||
- `system.network.dns.servers` and `system.network.dns.search` accept either YAML lists or comma-separated strings.
|
- `system.network.dns.servers` and `system.network.dns.search` must be YAML lists.
|
||||||
- `hypervisor.type` selects backend-specific provisioning and cleanup behavior.
|
- `hypervisor.type` selects backend-specific provisioning and cleanup behavior.
|
||||||
- Guest tools are selected automatically by hypervisor: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`.
|
- Guest tools are selected automatically by hypervisor: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`.
|
||||||
- With `system.luks.method: tpm2` on virtual installs, the virtualization role enables a TPM2 device where supported (libvirt/proxmox/vmware).
|
- With `system.luks.method: tpm2` on virtual installs, the virtualization role enables a TPM2 device where supported (libvirt/proxmox/vmware).
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ all:
|
|||||||
fstype: xfs
|
fstype: xfs
|
||||||
label: DATA
|
label: DATA
|
||||||
opts: defaults
|
opts: defaults
|
||||||
user:
|
users:
|
||||||
name: "ops"
|
- name: "ops"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
packages:
|
packages:
|
||||||
@@ -99,11 +99,11 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /srv/data
|
path: /srv/data
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
user:
|
users:
|
||||||
name: "dbadmin"
|
- name: "dbadmin"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /var/www
|
path: /var/www
|
||||||
fstype: xfs
|
fstype: xfs
|
||||||
user:
|
users:
|
||||||
name: "web"
|
- name: "web"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
packages:
|
packages:
|
||||||
@@ -81,11 +81,11 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
user:
|
users:
|
||||||
name: "db"
|
- name: "db"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
@@ -122,11 +122,11 @@ all:
|
|||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: btrfs
|
fstype: btrfs
|
||||||
user:
|
users:
|
||||||
name: "compute"
|
- name: "compute"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
features:
|
features:
|
||||||
|
|||||||
66
main.yml
66
main.yml
@@ -28,35 +28,59 @@
|
|||||||
- name: Apply prompted authentication values to system input
|
- name: Apply prompted authentication values to system input
|
||||||
vars:
|
vars:
|
||||||
system_input: "{{ system | default({}) }}"
|
system_input: "{{ system | default({}) }}"
|
||||||
system_user_input: "{{ (system_input.user | default({})) if (system_input.user is mapping) else {} }}"
|
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 {} }}"
|
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_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_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_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
|
||||||
prompt_root_password: "{{ root_password | default(system_root_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:
|
ansible.builtin.set_fact:
|
||||||
system: >-
|
system: >-
|
||||||
{{
|
{{
|
||||||
system_input
|
system_input
|
||||||
| combine(
|
| combine(
|
||||||
{
|
{
|
||||||
'user': {
|
'users': (
|
||||||
'name': (
|
[resolved_user]
|
||||||
(system_user_input.name | default('') | string | length) > 0
|
+ (system_users_input[1:]
|
||||||
) | ternary(system_user_input.name | string, prompt_user_name),
|
if (system_users_input is sequence
|
||||||
'keys': (
|
and system_users_input is not string
|
||||||
system_user_input.keys
|
and system_users_input | length > 1)
|
||||||
if (system_user_input.keys is iterable and system_user_input.keys is not string and system_user_input.keys | length > 0)
|
else [])
|
||||||
else (
|
),
|
||||||
[prompt_user_key]
|
|
||||||
if (prompt_user_key | length > 0)
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'password': (
|
|
||||||
(system_user_input.password | default('') | string | length) > 0
|
|
||||||
) | ternary(system_user_input.password | string, prompt_user_password)
|
|
||||||
},
|
|
||||||
'root': {
|
'root': {
|
||||||
'password': (
|
'password': (
|
||||||
(system_root_input.password | default('') | string | length) > 0
|
(system_root_input.password | default('') | string | length) > 0
|
||||||
@@ -124,9 +148,9 @@
|
|||||||
when:
|
when:
|
||||||
- post_reboot_can_connect | bool
|
- post_reboot_can_connect | bool
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_user: "{{ system_cfg.user.name }}"
|
ansible_user: "{{ system_cfg.users[0].name }}"
|
||||||
ansible_password: "{{ system_cfg.user.password }}"
|
ansible_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_become_password: "{{ system_cfg.user.password }}"
|
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
|
|
||||||
- name: Install post-reboot packages
|
- name: Install post-reboot packages
|
||||||
|
|||||||
@@ -5,3 +5,14 @@
|
|||||||
dest: /mnt/etc/sudoers.d/01-wheel
|
dest: /mnt/etc/sudoers.d/01-wheel
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: /usr/sbin/visudo --check --file=%s
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
|
|
||||||
|
- name: Deploy per-user sudoers rules
|
||||||
|
when: item.sudo is defined and (item.sudo | string | length) > 0
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.name }} {{ item.sudo }}\n"
|
||||||
|
dest: "/mnt/etc/sudoers.d/{{ item.name }}"
|
||||||
|
mode: "0440"
|
||||||
|
validate: /usr/sbin/visudo --check --file=%s
|
||||||
|
loop: "{{ system_cfg.users }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|||||||
@@ -1,38 +1,53 @@
|
|||||||
---
|
---
|
||||||
- name: Create user account
|
- name: Set root password
|
||||||
|
vars:
|
||||||
|
configuration_root_cmd: >-
|
||||||
|
{{ chroot_command }} /usr/sbin/usermod --password
|
||||||
|
'{{ system_cfg.root.password | password_hash('sha512') }}' root --shell /bin/bash
|
||||||
|
ansible.builtin.command: "{{ configuration_root_cmd }}"
|
||||||
|
register: configuration_root_result
|
||||||
|
changed_when: configuration_root_result.rc == 0
|
||||||
|
|
||||||
|
- name: Create user accounts
|
||||||
vars:
|
vars:
|
||||||
configuration_user_group: >-
|
configuration_user_group: >-
|
||||||
{{ "sudo" if is_debian | bool else "wheel" }}
|
{{ "sudo" if is_debian | bool else "wheel" }}
|
||||||
configuration_useradd_cmd: >-
|
configuration_useradd_cmd: >-
|
||||||
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
|
||||||
--groups {{ configuration_user_group }} {{ system_cfg.user.name }}
|
--uid {{ 1000 + ansible_loop.index0 }}
|
||||||
--password {{ system_cfg.user.password | password_hash('sha512') }} --shell /bin/bash
|
--groups {{ configuration_user_group }} {{ item.name }}
|
||||||
configuration_root_cmd: >-
|
--password {{ item.password | password_hash('sha512') }} --shell /bin/bash
|
||||||
{{ chroot_command }} /usr/sbin/usermod --password
|
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
|
||||||
'{{ system_cfg.root.password | password_hash('sha512') }}' root --shell /bin/bash
|
loop: "{{ system_cfg.users }}"
|
||||||
ansible.builtin.command: "{{ item }}"
|
loop_control:
|
||||||
loop:
|
extended: true
|
||||||
- "{{ configuration_useradd_cmd }}"
|
label: "{{ item.name }}"
|
||||||
- "{{ configuration_root_cmd }}"
|
|
||||||
register: configuration_user_result
|
register: configuration_user_result
|
||||||
changed_when: configuration_user_result.rc == 0
|
changed_when: configuration_user_result.rc == 0
|
||||||
|
|
||||||
- name: Ensure .ssh directory exists
|
- name: Ensure .ssh directory exists
|
||||||
when: system_cfg.user.keys | length > 0
|
when: item.keys | default([]) | length > 0
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /mnt/home/{{ system_cfg.user.name }}/.ssh
|
path: "/mnt/home/{{ item.name }}/.ssh"
|
||||||
state: directory
|
state: directory
|
||||||
owner: 1000
|
owner: "{{ 1000 + ansible_loop.index0 }}"
|
||||||
group: 1000
|
group: "{{ 1000 + ansible_loop.index0 }}"
|
||||||
mode: "0700"
|
mode: "0700"
|
||||||
|
loop: "{{ system_cfg.users }}"
|
||||||
|
loop_control:
|
||||||
|
extended: true
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
- name: Add SSH public keys to authorized_keys
|
- name: Add SSH public keys to authorized_keys
|
||||||
when: system_cfg.user.keys | length > 0
|
vars:
|
||||||
|
_uid: "{{ 1000 + (system_cfg.users | map(attribute='name') | list).index(item.0.name) }}"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /mnt/home/{{ system_cfg.user.name }}/.ssh/authorized_keys
|
path: "/mnt/home/{{ item.0.name }}/.ssh/authorized_keys"
|
||||||
line: "{{ item }}"
|
line: "{{ item.1 }}"
|
||||||
owner: 1000
|
owner: "{{ _uid }}"
|
||||||
group: 1000
|
group: "{{ _uid }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
create: true
|
create: true
|
||||||
loop: "{{ system_cfg.user.keys }}"
|
loop: "{{ system_cfg.users | subelements('keys', skip_missing=True) }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.0.name }}: {{ item.1[:40] }}..."
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ system_defaults:
|
|||||||
path: ""
|
path: ""
|
||||||
packages: []
|
packages: []
|
||||||
disks: []
|
disks: []
|
||||||
user:
|
users: []
|
||||||
name: ""
|
|
||||||
password: ""
|
|
||||||
keys: []
|
|
||||||
root:
|
root:
|
||||||
password: ""
|
password: ""
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
@@ -66,9 +66,9 @@
|
|||||||
- system_cfg.type == "virtual"
|
- system_cfg.type == "virtual"
|
||||||
- hypervisor_type != "vmware"
|
- hypervisor_type != "vmware"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
ansible_user: "{{ system_cfg.user.name }}"
|
ansible_user: "{{ system_cfg.users[0].name }}"
|
||||||
ansible_password: "{{ system_cfg.user.password }}"
|
ansible_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_become_password: "{{ system_cfg.user.password }}"
|
ansible_become_password: "{{ system_cfg.users[0].password }}"
|
||||||
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
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.user is not defined or system.user 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, user, root, luks, features) 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 and user.keys are lists (not strings)
|
- name: Validate DNS lists (not strings)
|
||||||
when: system.network is defined and system.network.dns is defined
|
when: system.network is defined and system.network.dns is defined
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
@@ -25,13 +25,18 @@
|
|||||||
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
|
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate user.keys is a list
|
- name: Validate system.users entries
|
||||||
when: system.user is defined and system.user.keys is defined
|
when: system.users is defined and system.users | length > 0
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- system.user.keys is iterable and system.user.keys is not string
|
- item is mapping
|
||||||
fail_msg: "system.user.keys must be a list of SSH public key strings."
|
- item.name is defined and (item.name | string | length) > 0
|
||||||
|
- 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 with 'name'; 'keys' must be a list."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
loop: "{{ system.users }}"
|
||||||
|
loop_control:
|
||||||
|
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
|
||||||
@@ -122,10 +127,7 @@
|
|||||||
| list
|
| list
|
||||||
}}
|
}}
|
||||||
disks: "{{ system_raw.disks | default([]) }}"
|
disks: "{{ system_raw.disks | default([]) }}"
|
||||||
user:
|
users: "{{ system_raw.users | default([]) }}"
|
||||||
name: "{{ system_raw.user.name | string }}"
|
|
||||||
password: "{{ system_raw.user.password | string }}"
|
|
||||||
keys: "{{ system_raw.user.keys | default([]) }}"
|
|
||||||
root:
|
root:
|
||||||
password: "{{ system_raw.root.password | string }}"
|
password: "{{ system_raw.root.password | string }}"
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
@@ -48,25 +48,9 @@
|
|||||||
fail_msg: "Unsupported system keys: {{ system_unknown_keys | join(', ') }}."
|
fail_msg: "Unsupported system keys: {{ system_unknown_keys | join(', ') }}."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Validate nested system mappings
|
|
||||||
loop:
|
|
||||||
- network
|
|
||||||
- user
|
|
||||||
- root
|
|
||||||
- luks
|
|
||||||
- features
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- system[item] is not defined or system[item] is mapping
|
|
||||||
fail_msg: "system.{{ item }} must be a dictionary."
|
|
||||||
quiet: true
|
|
||||||
|
|
||||||
- name: Validate system sub-dict schemas
|
- name: Validate system sub-dict schemas
|
||||||
loop:
|
loop:
|
||||||
- network
|
- network
|
||||||
- user
|
|
||||||
- root
|
- root
|
||||||
- luks
|
- luks
|
||||||
loop_control:
|
loop_control:
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
api_host: "{{ hypervisor_cfg.url }}"
|
api_host: "{{ hypervisor_cfg.url }}"
|
||||||
api_user: "{{ hypervisor_cfg.username }}"
|
api_user: "{{ hypervisor_cfg.username }}"
|
||||||
api_password: "{{ hypervisor_cfg.password }}"
|
api_password: "{{ hypervisor_cfg.password }}"
|
||||||
ciuser: "{{ system_cfg.user.name }}"
|
ciuser: "{{ system_cfg.users[0].name }}"
|
||||||
cipassword: "{{ system_cfg.user.password }}"
|
cipassword: "{{ system_cfg.users[0].password }}"
|
||||||
ciupgrade: false
|
ciupgrade: false
|
||||||
node: "{{ hypervisor_cfg.host }}"
|
node: "{{ hypervisor_cfg.host }}"
|
||||||
vmid: "{{ system_cfg.id }}"
|
vmid: "{{ system_cfg.id }}"
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ ssh_pwauth: true
|
|||||||
package_update: false
|
package_update: false
|
||||||
package_upgrade: false
|
package_upgrade: false
|
||||||
users:
|
users:
|
||||||
- name: "{{ system_cfg.user.name }}"
|
{% for user in system_cfg.users %}
|
||||||
primary_group: "{{ system_cfg.user.name }}"
|
- name: "{{ user.name }}"
|
||||||
|
primary_group: "{{ user.name }}"
|
||||||
groups: users
|
groups: users
|
||||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
sudo: "{{ user.sudo | default('ALL=(ALL) NOPASSWD:ALL') }}"
|
||||||
passwd: "{{ system_cfg.user.password | password_hash('sha512') }}"
|
passwd: "{{ user.password | password_hash('sha512') }}"
|
||||||
lock_passwd: False
|
lock_passwd: false
|
||||||
|
{% set ssh_keys = user.keys | default([]) %}
|
||||||
|
{% if ssh_keys | length > 0 %}
|
||||||
|
ssh_authorized_keys:
|
||||||
|
{% for key in ssh_keys %}
|
||||||
|
- "{{ key }}"
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ system:
|
|||||||
mount:
|
mount:
|
||||||
path: /data
|
path: /data
|
||||||
fstype: ext4
|
fstype: ext4
|
||||||
user:
|
users:
|
||||||
name: "admin"
|
- name: "admin"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ system:
|
|||||||
fstype: xfs
|
fstype: xfs
|
||||||
label: DATA
|
label: DATA
|
||||||
opts: defaults
|
opts: defaults
|
||||||
user:
|
users:
|
||||||
name: "ops"
|
- name: "ops"
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
keys:
|
keys:
|
||||||
- "ssh-ed25519 AAAA..."
|
- "ssh-ed25519 AAAA..."
|
||||||
root:
|
root:
|
||||||
password: "CHANGE_ME"
|
password: "CHANGE_ME"
|
||||||
luks:
|
luks:
|
||||||
|
|||||||
Reference in New Issue
Block a user