From 3e85a44d8ea4128a5129baf3653de686a5668eb0 Mon Sep 17 00:00:00 2001 From: MORAWSKI Norbert Date: Fri, 20 Mar 2026 15:06:32 +0100 Subject: [PATCH] refactor(prompts): remove vars_prompt, require users defined in inventory --- README.md | 25 +++++++++++++++---- main.yml | 75 ------------------------------------------------------- 2 files changed, 20 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 9ae83e4..858f3f7 100644 --- a/README.md +++ b/README.md @@ -202,14 +202,29 @@ When `interfaces` is empty, the flat fields (`bridge`, `ip`, `prefix`, `gateway` #### `system.users` +Dict keyed by username. At least one user must have a `password` (used for SSH access during bootstrap). Users without a password get locked accounts (key-only auth). + +```yaml +system: + users: + svcansible: + password: "vault_lookup" + keys: + - "ssh-ed25519 AAAA..." + appuser: + sudo: "ALL=(ALL) NOPASSWD: ALL" + keys: + - "ssh-ed25519 BBBB..." +``` + | Key | Type | Default | Description | | ---------- | ----------- | ------- | -------------------------------------------------- | -| `name` | string | -- | Username (required) | -| `password` | string | -- | User password (required for first user) | +| *(dict key)* | string | -- | Username (required) | +| `password` | string | -- | User password (required for at least one user) | | `keys` | list | `[]` | SSH public keys | | `sudo` | bool/string | -- | `true` for NOPASSWD ALL, or custom sudoers string | -The first user's credentials are prompted interactively via `vars_prompt` unless supplied in inventory or `-e`. +Users must be defined in inventory. The dict format enables additive merging across inventory layers with `hash_behaviour=merge`. #### `system.root` @@ -398,7 +413,7 @@ ansible-playbook -i inventory.yml main.yml ansible-playbook -i inventory.yml main.yml -e @vars.yml ``` -Credentials for the first user and root are prompted interactively via `vars_prompt` unless already set in inventory or passed via `-e`. +All credentials (`system.users`, `system.root.password`) must be defined in inventory or passed via `-e`. Example inventory files are included: @@ -408,7 +423,7 @@ Example inventory files are included: ## 7. Security -Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, `system.users[].password`, `system.root.password`). +Use **Ansible Vault** for all sensitive values (`hypervisor.password`, `system.luks.passphrase`, user passwords in `system.users`, `system.root.password`). ## 8. Safety diff --git a/main.yml b/main.yml index d9a429f..0f8c836 100644 --- a/main.yml +++ b/main.yml @@ -14,82 +14,7 @@ strategy: free # noqa: run-once[play] gather_facts: false become: true - vars_prompt: - - name: user_name - prompt: | - What is your username? - default: "" - private: false - - - name: user_public_key - prompt: | - What is your ssh key? - default: "" - private: false - - - name: user_password - prompt: | - What is your password? - default: "" - confirm: true - - - name: root_password - prompt: | - What is your root password? - default: "" - confirm: true 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({}) }}" - _first_entry: "{{ system_users_input | dict2items | first | default({'key': '', 'value': {}}) }}" - _first_name: "{{ _first_entry.key }}" - _first_attrs: "{{ _first_entry.value if _first_entry.value is mapping else {} }}" - system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}" - prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}" - prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string | 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_name: "{{ _first_name if (_first_name | length > 0) else prompt_user_name }}" - resolved_attrs: - keys: >- - {{ - _first_attrs['keys'] - if (_first_attrs['keys'] is defined - and _first_attrs['keys'] is iterable - and _first_attrs['keys'] is not string - and _first_attrs['keys'] | length > 0) - else ( - [prompt_user_key] - if (prompt_user_key | length > 0) - else [] - ) - }} - password: >- - {{ - _first_attrs.password | string - if (_first_attrs.password | default('') | string | length) > 0 - else prompt_user_password - }} - ansible.builtin.set_fact: - system: >- - {{ - system_input - | combine( - { - 'users': system_users_input | combine({resolved_name: (_first_attrs | combine(resolved_attrs, recursive=True))}), - '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 ansible.builtin.import_role: name: global_defaults