Compare commits
19 Commits
130ad1dba5
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 54dfc77e78 | |||
| 6cdfa56880 | |||
| 45de5696f7 | |||
| 53da333970 | |||
| 8530b93fea | |||
| 130fcb5069 | |||
| 93cee75103 | |||
| 24f34198ff | |||
| f29de42397 | |||
| daa5826573 | |||
| 0696218eb6 | |||
| 4789bbd850 | |||
| e5e4aa1832 | |||
| 74f567c8eb | |||
| 996e242180 | |||
| 9627a4a5e0 | |||
| e20776a571 | |||
| 2fccf20768 | |||
| a5200c8d9b |
@@ -0,0 +1,97 @@
|
|||||||
|
name: "Bug Report"
|
||||||
|
description: "Report a bug or unexpected behavior to help us improve"
|
||||||
|
title: "[BUG]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## ⚠️ Before You Begin
|
||||||
|
Please ensure you have:
|
||||||
|
- [ ] Searched existing issues to avoid duplicates
|
||||||
|
- [ ] Confirmed this is a bug, not a question or configuration problem
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: "📋 Description"
|
||||||
|
description: "Provide a clear and concise description of the bug"
|
||||||
|
placeholder: "What happened? What did you expect to happen instead?"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: "🔁 Steps to Reproduce"
|
||||||
|
description: "Step-by-step instructions to reproduce the issue"
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: "✅ Expected Behavior"
|
||||||
|
description: "What you expected to happen"
|
||||||
|
placeholder: "A clear description of what should happen..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: "❌ Actual Behavior"
|
||||||
|
description: "What actually happened"
|
||||||
|
placeholder: "Include error messages, stack traces, or unexpected outcomes..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: "📄 Logs / Screenshots"
|
||||||
|
description: |
|
||||||
|
Provide relevant logs, error messages, or screenshots.
|
||||||
|
For logs, please use a pastebin and share the URL.
|
||||||
|
**Remember to remove any sensitive information (API keys, passwords, etc.).**
|
||||||
|
placeholder: "Paste logs here or provide a Gist URL..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
## 🖥️ Environment Details
|
||||||
|
Please fill out the relevant information below.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: "📎 Additional Context"
|
||||||
|
description: "Any other information that might be relevant"
|
||||||
|
placeholder: |
|
||||||
|
- Database type and version (e.g., PostgreSQL 15, SQLite)
|
||||||
|
- Reverse proxy/CDN in use (e.g., Nginx, Cloudflare)
|
||||||
|
- Any custom configuration
|
||||||
|
- Related issues or PRs
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: "✅ Submission Checklist"
|
||||||
|
description: "Please confirm the following before submitting"
|
||||||
|
options:
|
||||||
|
- label: "I have searched for existing issues (open and closed) that report the same problem"
|
||||||
|
required: true
|
||||||
|
- label: "I have provided clear steps to reproduce the issue"
|
||||||
|
required: true
|
||||||
|
- label: "I have included relevant logs or error messages (with sensitive info removed)"
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
name: "Feature Request"
|
||||||
|
description: "Suggest a new feature or enhancement for this project"
|
||||||
|
title: "[FEATURE]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## ⚠️ Before You Begin
|
||||||
|
Please ensure you have:
|
||||||
|
- [ ] Searched existing issues (open and closed) to avoid duplicates
|
||||||
|
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/vmn-ansible/wiki) for existing functionality
|
||||||
|
- [ ] Confirmed this is a feature request, not a bug or configuration question
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: "🎯 Problem Statement"
|
||||||
|
description: "What problem does this feature solve? What can't you do today?"
|
||||||
|
placeholder: |
|
||||||
|
I'm always frustrated when...
|
||||||
|
Currently, it's hard/impossible to...
|
||||||
|
This feature would help me because...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: "💡 Proposed Solution"
|
||||||
|
description: "Describe the solution you'd like to see"
|
||||||
|
placeholder: |
|
||||||
|
A clear description of what you want to happen.
|
||||||
|
If you have a specific API, UI, or implementation in mind, describe it here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: "🔄 Alternatives Considered"
|
||||||
|
description: "What alternative solutions or workarounds have you considered?"
|
||||||
|
placeholder: |
|
||||||
|
- Alternative A: ...
|
||||||
|
- Workaround B: ...
|
||||||
|
- Why these don't fully solve the problem
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: "📸 Screenshots"
|
||||||
|
description: "If you can, provide screenshots or diagrams of the proposed feature"
|
||||||
|
placeholder: "Drag and drop images or paste links..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
---
|
||||||
|
## 🔧 Implementation Details (Optional)
|
||||||
|
The following sections are for contributors who want to implement this feature.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: technical
|
||||||
|
attributes:
|
||||||
|
label: "⚙️ Technical Approach"
|
||||||
|
description: "If you have ideas about how to implement this, share them here"
|
||||||
|
placeholder: |
|
||||||
|
- Which components/modules would be affected?
|
||||||
|
- Any database schema changes?
|
||||||
|
- API design considerations?
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: acceptance
|
||||||
|
attributes:
|
||||||
|
label: "✅ Acceptance Criteria"
|
||||||
|
description: "What does 'done' look like for this feature?"
|
||||||
|
placeholder: |
|
||||||
|
- [ ] Feature works in the UI
|
||||||
|
- [ ] API endpoints are documented
|
||||||
|
- [ ] Tests are added
|
||||||
|
- [ ] Documentation is updated
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: contribution
|
||||||
|
attributes:
|
||||||
|
label: "🤝 Are you willing to contribute this feature?"
|
||||||
|
description: "Knowing if you can help implement this helps us prioritize"
|
||||||
|
options:
|
||||||
|
- "Yes, I can contribute"
|
||||||
|
- "Yes, but I need guidance"
|
||||||
|
- "No, but I can test/review"
|
||||||
|
- "No, I'm just requesting"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: "✅ Submission Checklist"
|
||||||
|
description: "Please confirm the following before submitting"
|
||||||
|
options:
|
||||||
|
- label: "I have searched for existing issues (open and closed) that request the same feature"
|
||||||
|
required: true
|
||||||
|
- label: "I have explained the problem this feature solves"
|
||||||
|
required: true
|
||||||
|
- label: "I have described a concrete solution or direction"
|
||||||
|
required: true
|
||||||
|
- label: "I have considered alternatives and explained why they fall short"
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
name: "Refactor Request"
|
||||||
|
description: "Propose a code restructuring or cleanup with no behavior change"
|
||||||
|
title: "[REFACTOR]: "
|
||||||
|
labels: ["refactoring"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## ⚠️ Before You Begin
|
||||||
|
Please ensure you have:
|
||||||
|
- [ ] Searched existing issues (open and closed) to avoid duplicates
|
||||||
|
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/vmn-ansible/wiki) for architecture guidelines
|
||||||
|
- [ ] Confirmed this is a refactor (code restructuring with NO behavior change), not a bug fix or new feature
|
||||||
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: "🎯 Problem Statement"
|
||||||
|
description: "What pain points does the current code cause? Why is this refactor needed?"
|
||||||
|
placeholder: |
|
||||||
|
The current implementation is difficult to maintain because...
|
||||||
|
This code has accumulated technical debt due to...
|
||||||
|
I'm constantly frustrated when working with this module because...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: scope
|
||||||
|
attributes:
|
||||||
|
label: "📂 Scope"
|
||||||
|
description: "Which files, modules, or components need refactoring? Be specific."
|
||||||
|
placeholder: |
|
||||||
|
- File paths: `src/auth/`, `internal/handler/`
|
||||||
|
- Component: `UserService` class
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: current-state
|
||||||
|
attributes:
|
||||||
|
label: "🔍 Current State"
|
||||||
|
description: "Describe the current code structure, its issues, and why it's problematic"
|
||||||
|
placeholder: |
|
||||||
|
- The function `processUser()` is 500+ lines long
|
||||||
|
- Duplicated logic across 3 different files
|
||||||
|
- Mixed concerns (business logic + HTTP handling)
|
||||||
|
- Poor test coverage makes changes risky
|
||||||
|
- Naming is unclear and inconsistent
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposed-changes
|
||||||
|
attributes:
|
||||||
|
label: "💡 Proposed Solution"
|
||||||
|
description: "What should the code look like after the refactor?"
|
||||||
|
placeholder: |
|
||||||
|
- Extract `processUser()` into smaller, focused functions
|
||||||
|
- Consolidate duplicated logic into a shared utility
|
||||||
|
- Separate business logic from HTTP handlers
|
||||||
|
- Introduce interfaces for better testability
|
||||||
|
- Rename variables and functions for clarity
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: constraints
|
||||||
|
attributes:
|
||||||
|
label: "🚫 Constraints"
|
||||||
|
description: "What must NOT change? Public APIs, behavior, backward compatibility, performance characteristics?"
|
||||||
|
placeholder: |
|
||||||
|
- All public APIs must remain identical
|
||||||
|
- Database schema must not change
|
||||||
|
- External behavior must be identical
|
||||||
|
- Response formats must stay the same
|
||||||
|
- Performance must not degrade
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: benefits
|
||||||
|
attributes:
|
||||||
|
label: "📈 Expected Benefits"
|
||||||
|
description: "What improvements will this refactor bring?"
|
||||||
|
placeholder: |
|
||||||
|
- Improved maintainability
|
||||||
|
- Better testability
|
||||||
|
- Reduced code duplication
|
||||||
|
- Clearer separation of concerns
|
||||||
|
- Easier onboarding for new contributors
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: risks
|
||||||
|
attributes:
|
||||||
|
label: "⚠️ Risks & Mitigations"
|
||||||
|
description: "What could go wrong, and how will you mitigate it?"
|
||||||
|
placeholder: |
|
||||||
|
- Risk: Regression bugs
|
||||||
|
Mitigation: Comprehensive test coverage before and after
|
||||||
|
- Risk: Large diff making code review difficult
|
||||||
|
Mitigation: Break into smaller, incremental PRs
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: "🔄 Alternatives Considered"
|
||||||
|
description: "What other approaches did you consider, and why did you reject them?"
|
||||||
|
placeholder: |
|
||||||
|
- Alternative A: Complete rewrite — rejected because too risky
|
||||||
|
- Alternative B: Gradual deprecation — rejected because too slow
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: contribution
|
||||||
|
attributes:
|
||||||
|
label: "🤝 Are you willing to contribute this refactor?"
|
||||||
|
description: "Knowing if you can help implement this helps us prioritize"
|
||||||
|
options:
|
||||||
|
- "Yes, I can contribute"
|
||||||
|
- "Yes, but I need guidance"
|
||||||
|
- "No, but I can review"
|
||||||
|
- "No, I'm just suggesting"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: "✅ Submission Checklist"
|
||||||
|
description: "Please confirm the following before submitting"
|
||||||
|
options:
|
||||||
|
- label: "I have searched for existing issues (open and closed) that request the same refactor"
|
||||||
|
required: true
|
||||||
|
- label: "I have clearly explained the problem this refactor solves"
|
||||||
|
required: true
|
||||||
|
- label: "I have described the scope of the refactor (specific files/modules)"
|
||||||
|
required: false
|
||||||
|
- label: "I have identified what must NOT change (APIs, behavior, backward compatibility)"
|
||||||
|
required: false
|
||||||
@@ -10,70 +10,36 @@
|
|||||||
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
|
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
|
||||||
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
|
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
|
||||||
|
|
||||||
vars_prompt:
|
pre_tasks:
|
||||||
- name: lxc_hostname
|
- name: Include input and validation for hostname
|
||||||
prompt: "Hostname for the container (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)"
|
ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml
|
||||||
default: "new-lxc-test"
|
|
||||||
private: false
|
- name: Include input and validation for LXC root password
|
||||||
|
ansible.builtin.include_tasks: ../tasks/lxc_root_password_validation.yaml
|
||||||
|
|
||||||
- name: lxc_root_password
|
- name: Include input and validation for IP
|
||||||
prompt: "Root password"
|
ansible.builtin.include_tasks: ../tasks/ip_validation.yaml
|
||||||
default: "new-lxc-test"
|
|
||||||
private: true
|
|
||||||
confirm: true
|
|
||||||
encrypt: sha512_crypt
|
|
||||||
|
|
||||||
- name: lxc_ip_address
|
- name: Include input and validation for LXC privilege
|
||||||
prompt: "IP address for the container (in x.x.x.x/x formant)"
|
ansible.builtin.include_tasks: ../tasks/lxc_privileged_validation.yaml
|
||||||
default: "192.168.0.90/24"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: lxc_is_privileged
|
- name: Include input and validation for OS template
|
||||||
prompt: "Unprivileged LXC (true/false)?"
|
ansible.builtin.include_tasks: ../tasks/os_template_validation.yaml
|
||||||
default: true
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: lxc_create_human_admin_user
|
- name: Include input and validation for human admin user creation
|
||||||
prompt: "Create human admin user? (true/false)?"
|
ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml
|
||||||
default: true
|
|
||||||
private: false
|
|
||||||
|
|
||||||
pre_tasks:
|
- name: Include input and validation for storage selection
|
||||||
- name: Validate hostname
|
ansible.builtin.include_tasks: ../tasks/storage_validation.yaml
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: |
|
|
||||||
Invalid hostname: "{{ lxc_hostname }}"
|
|
||||||
Hostname may only contain letters (a-z, A-Z), numbers (0-9), and hyphens (-).
|
|
||||||
when: not lxc_hostname is regex('^[a-zA-Z0-9-]+$')
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Validate IP address
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: |
|
|
||||||
Invalid IP address: "{{ lxc_ip_address }}"
|
|
||||||
IP address must be in x.x.x.x/x format (e.g., 192.168.0.90/24).
|
|
||||||
when: not lxc_ip_address is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(\\/(3[0-2]|[12]?\\d))?$')
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- ../roles/deploy_lxc_on_proxmox
|
- ../roles/deploy_lxc_on_proxmox
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Make the prompted hostname available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_lxc_hostname: "{{ lxc_hostname }}"
|
|
||||||
|
|
||||||
- name: Make the prompted IP available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_lxc_ip_address: "{{ lxc_ip_address.split('/') | first }}"
|
|
||||||
|
|
||||||
- name: Make the "human admin user" selection available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_lxc_create_human_admin_user: "{{ lxc_create_human_admin_user }}"
|
|
||||||
|
|
||||||
- name: Add the target host to the inventory
|
- name: Add the target host to the inventory
|
||||||
ansible.builtin.add_host:
|
ansible.builtin.add_host:
|
||||||
name: "{{ fact_lxc_ip_address }}"
|
name: "{{ guest_ip_address }}"
|
||||||
groups: new_host
|
groups: new_host
|
||||||
ansible_user: ansible
|
ansible_user: ansible
|
||||||
ansible_become_password: "{{ ansible_become_passwd }}"
|
ansible_become_password: "{{ ansible_become_passwd }}"
|
||||||
@@ -98,10 +64,10 @@
|
|||||||
- name: Include human admin user role
|
- name: Include human admin user role
|
||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: ../roles/human_admin_user
|
name: ../roles/human_admin_user
|
||||||
when: "hostvars['localhost']['fact_lxc_create_human_admin_user'] | bool"
|
when: "hostvars['localhost']['create_human_admin_user'] | bool"
|
||||||
|
|
||||||
- name: Include update configs role
|
- name: Include update configs role
|
||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: ../roles/update_configs
|
name: ../roles/update_configs
|
||||||
when: "hostvars['localhost']['fact_lxc_create_human_admin_user'] | bool"
|
when: "hostvars['localhost']['create_human_admin_user'] | bool"
|
||||||
|
|
||||||
|
|||||||
@@ -10,92 +10,36 @@
|
|||||||
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
|
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
|
||||||
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
|
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
|
||||||
|
|
||||||
vars_prompt:
|
|
||||||
- name: vm_hostname
|
|
||||||
prompt: "Hostname for the VM (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)"
|
|
||||||
default: "new-vm-test"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: vm_os_template
|
|
||||||
prompt: "Select OS template. Available: debian-13 | alpine"
|
|
||||||
default: "debian-13"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: vm_become_method
|
|
||||||
prompt: "Select become method. Available: sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl"
|
|
||||||
default: "sudo"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: vm_create_human_admin_user
|
|
||||||
prompt: "Create human admin user? (true/false)?"
|
|
||||||
default: true
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: vm_ip_address
|
|
||||||
prompt: "IP address for the VM (in x.x.x.x/x formant)"
|
|
||||||
default: "192.168.0.89/24"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
- name: Validate hostname
|
- name: Include input and validation for hostname
|
||||||
ansible.builtin.fail:
|
ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml
|
||||||
msg: |
|
|
||||||
Invalid hostname: "{{ vm_hostname }}"
|
|
||||||
Hostname may only contain letters (a-z, A-Z), numbers (0-9), and hyphens (-).
|
|
||||||
when: not vm_hostname is regex('^[a-zA-Z0-9-]+$')
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Validate IP address
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: |
|
|
||||||
Invalid IP address: "{{ vm_ip_address }}"
|
|
||||||
IP address must be in x.x.x.x/x format (e.g., 192.168.0.89/24).
|
|
||||||
when: not vm_ip_address is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(\\/(3[0-2]|[12]?\\d))?$')
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Validate OS template
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: |
|
|
||||||
Invalid OS template: "{{ vm_os_template }}"
|
|
||||||
Select from available options: debian-13, alpine
|
|
||||||
when: vm_os_template not in ['debian-13', 'alpine']
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Validate become method
|
- name: Include input and validation for IP
|
||||||
ansible.builtin.fail:
|
ansible.builtin.include_tasks: ../tasks/ip_validation.yaml
|
||||||
msg: |
|
|
||||||
Invalid become method: "{{ vm_become_method }}"
|
- name: Include input and validation for human admin user creation
|
||||||
Select from available options: sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl
|
ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml
|
||||||
when: vm_become_method not in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl']
|
|
||||||
run_once: true
|
- name: Include input and validation for OS template
|
||||||
|
ansible.builtin.include_tasks: ../tasks/os_template_validation.yaml
|
||||||
|
|
||||||
|
- name: Include input and validation for become method
|
||||||
|
ansible.builtin.include_tasks: ../tasks/become_method_validation.yaml
|
||||||
|
|
||||||
|
- name: Include input and validation for storage selection
|
||||||
|
ansible.builtin.include_tasks: ../tasks/storage_validation.yaml
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- ../roles/deploy_vm_on_proxmox
|
- ../roles/deploy_vm_on_proxmox
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Make the hostname available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_vm_hostname: "{{ vm_hostname }}"
|
|
||||||
|
|
||||||
- name: Make the OS template available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_vm_os_template: "{{ vm_os_template }}"
|
|
||||||
|
|
||||||
- name: Make the IP available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_vm_ip_address: "{{ vm_ip_address.split('/') | first }}"
|
|
||||||
|
|
||||||
- name: Make the "human admin user" selection available to the whole playbook
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
fact_vm_create_human_admin_user: "{{ vm_create_human_admin_user }}"
|
|
||||||
|
|
||||||
- name: Add the target host to the inventory
|
- name: Add the target host to the inventory
|
||||||
ansible.builtin.add_host:
|
ansible.builtin.add_host:
|
||||||
name: "{{ fact_vm_ip_address }}"
|
name: "{{ guest_ip_address }}"
|
||||||
groups: new_host
|
groups: new_host
|
||||||
ansible_user: ansible
|
ansible_user: ansible
|
||||||
ansible_become_password: "{{ ansible_become_passwd }}"
|
ansible_become_password: "{{ ansible_become_passwd }}"
|
||||||
ansible_become_method: "{{ vm_become_method }}"
|
ansible_become_method: "{{ guest_become_method }}"
|
||||||
|
|
||||||
|
|
||||||
- name: Configure VM
|
- name: Configure VM
|
||||||
@@ -117,10 +61,10 @@
|
|||||||
- name: Include human admin user role
|
- name: Include human admin user role
|
||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: ../roles/human_admin_user
|
name: ../roles/human_admin_user
|
||||||
when: "hostvars['localhost']['fact_vm_create_human_admin_user'] | bool"
|
when: "hostvars['localhost']['create_human_admin_user'] | bool"
|
||||||
|
|
||||||
- name: Include update configs role
|
- name: Include update configs role
|
||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: ../roles/update_configs
|
name: ../roles/update_configs
|
||||||
when: "hostvars['localhost']['fact_vm_create_human_admin_user'] | bool"
|
when: "hostvars['localhost']['create_human_admin_user'] | bool"
|
||||||
|
|
||||||
|
|||||||
@@ -31,3 +31,19 @@
|
|||||||
roles:
|
roles:
|
||||||
# Pull certificates from cert_proxy and distribute them among the servers
|
# Pull certificates from cert_proxy and distribute them among the servers
|
||||||
- ../roles/update_certificates
|
- ../roles/update_certificates
|
||||||
|
|
||||||
|
post_tasks:
|
||||||
|
- name: Delete certificates from cert-proxy
|
||||||
|
ansible.builtin.file:
|
||||||
|
name: /tmp/letsencrypt
|
||||||
|
state: absent
|
||||||
|
when: "inventory_hostname == 'cert-proxy'"
|
||||||
|
run_once: true
|
||||||
|
|
||||||
|
- name: Delete certificates from ansible-server
|
||||||
|
ansible.builtin.file:
|
||||||
|
name: /tmp/letsencrypt
|
||||||
|
state: absent
|
||||||
|
run_once: true
|
||||||
|
delegate_to: 127.0.0.1
|
||||||
|
|
||||||
|
|||||||
@@ -49,4 +49,5 @@
|
|||||||
name: qemu-guest-agent
|
name: qemu-guest-agent
|
||||||
enabled: yes
|
enabled: yes
|
||||||
state: started
|
state: started
|
||||||
|
when: ansible_facts['virtualization_type'] == "kvm"
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,22 @@
|
|||||||
become: true
|
become: true
|
||||||
|
|
||||||
# The same commands for Alpine
|
# The same commands for Alpine
|
||||||
- name: Update and install packages on Alpine
|
- name: Install sudo on Alpine
|
||||||
when: (ansible_facts['distribution'] == "Alpine")
|
when: (ansible_facts['distribution'] == "Alpine")
|
||||||
community.general.apk:
|
community.general.apk:
|
||||||
name: sudo
|
name: sudo
|
||||||
update_cache: true
|
update_cache: true
|
||||||
remote_user: ansible
|
remote_user: ansible
|
||||||
|
|
||||||
|
- name: Enable sudo for root and human_admin_user
|
||||||
|
when: (ansible_facts['distribution'] == "Alpine")
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/sudoers.d/80-ansible-created-users
|
||||||
|
content: |
|
||||||
|
root ALL=(ALL) ALL:ALL
|
||||||
|
{{ human_admin_user }} ALL=(ALL) ALL:ALL
|
||||||
|
remote_user: ansible
|
||||||
|
|
||||||
|
|
||||||
## Creating and setting up the ansible user
|
## Creating and setting up the ansible user
|
||||||
## First, create sshusers group to grant ssh access
|
## First, create sshusers group to grant ssh access
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
|
|
||||||
cmode: "shell"
|
cmode: "shell"
|
||||||
password: "{{ lxc_root_password }}"
|
password: "{{ lxc_root_password }}"
|
||||||
hostname: "{{ lxc_hostname }}"
|
hostname: "{{ guest_hostname }}"
|
||||||
ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz'
|
ostemplate: "main:vztmpl/{{ guest_os_template }}-golden-image.tar.gz"
|
||||||
unprivileged: "{{ lxc_is_privileged }}"
|
unprivileged: "{{ not lxc_privileged | bool }}"
|
||||||
memory: 2048
|
memory: 2048
|
||||||
cores: 2
|
cores: 2
|
||||||
state: present
|
state: present
|
||||||
@@ -22,11 +22,13 @@
|
|||||||
# This is minimal requirement for rootfs
|
# This is minimal requirement for rootfs
|
||||||
# It might be resized later
|
# It might be resized later
|
||||||
size: 4 # 4GB rootfs
|
size: 4 # 4GB rootfs
|
||||||
# We put rootfs on fast local storage (NVMe SSD)
|
# We put rootfs on user-selected sotrage
|
||||||
storage: local
|
storage: "{{ guest_storage }}"
|
||||||
|
# We need timeout in case a slow storage is used, since it may cause a significant delay
|
||||||
|
timeout: 90
|
||||||
pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}"
|
pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}"
|
||||||
netif:
|
netif:
|
||||||
net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }},bridge=vmbr0"
|
net0: "name=eth0,gw=192.168.0.1,ip={{ guest_ip_address }}/{{ guest_netmask }},bridge=vmbr0"
|
||||||
|
|
||||||
- name: Make sure the container has started
|
- name: Make sure the container has started
|
||||||
become: false
|
become: false
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
hostname: "{{ lxc_hostname }}"
|
hostname: "{{ guest_hostname }}"
|
||||||
state: started
|
state: started
|
||||||
retries: 3
|
retries: 3
|
||||||
delay: 15
|
delay: 15
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
clone: "{{ vm_os_template }}-cloud-init-template"
|
clone: "{{ guest_os_template }}-cloud-init-template"
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
storage: main
|
storage: "{{ guest_storage }}"
|
||||||
format: qcow2
|
format: qcow2
|
||||||
full: true
|
full: true
|
||||||
timeout: 500
|
timeout: 500
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
# Cloud-init parameters
|
# Cloud-init parameters
|
||||||
update: true
|
update: true
|
||||||
ide:
|
ide:
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
ciuser: "ansible"
|
ciuser: "ansible"
|
||||||
cipassword: "{{ ansible_become_passwd }}"
|
cipassword: "{{ ansible_become_passwd }}"
|
||||||
ipconfig:
|
ipconfig:
|
||||||
ipconfig0: "ip={{ vm_ip_address }},gw=192.168.0.1"
|
ipconfig0: "ip={{ guest_ip_address }}/{{ guest_netmask }},gw=192.168.0.1"
|
||||||
|
|
||||||
- name: Tweak the hardware settings
|
- name: Tweak the hardware settings
|
||||||
become: false
|
become: false
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
cores: 4
|
cores: 4
|
||||||
memory: 4096 # Minimal value for debian based OS for smooth workflow
|
memory: 4096 # Minimal value for debian based OS for smooth workflow
|
||||||
update: true
|
update: true
|
||||||
@@ -68,5 +68,5 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
state: started
|
state: started
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
state: current
|
state: current
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: vm_status
|
register: vm_status
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
api_token_id: ansible
|
api_token_id: ansible
|
||||||
api_token_secret: "{{ proxmox_token_secret }}"
|
api_token_secret: "{{ proxmox_token_secret }}"
|
||||||
|
|
||||||
name: "{{ vm_hostname }}"
|
name: "{{ guest_hostname }}"
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: Sleep for 120 seconds to ensure that Proxmox has cloned the VM
|
- name: Sleep for 120 seconds to ensure that Proxmox has cloned the VM
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
FILE=/home/$USER/.oh-my-zsh/oh-my-zsh.sh
|
TARGET_USER=$1
|
||||||
|
|
||||||
|
FILE=/home/$TARGET_USER/.oh-my-zsh/oh-my-zsh.sh
|
||||||
if [ -f "$FILE" ]; then
|
if [ -f "$FILE" ]; then
|
||||||
echo "$FILE exists and we not installing ohmyzsh"
|
echo "$FILE exists and we not installing ohmyzsh"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "$FILE does not exist and we install ohmyzsh"
|
echo "$FILE does not exist and we install ohmyzsh"
|
||||||
cd /home/max
|
cd /home/$TARGET_USER
|
||||||
wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
sudo -u $TARGET_USER wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
||||||
chmod +x /home/max/install.sh
|
chmod +x /home/$TARGET_USER/install.sh
|
||||||
timeout -s 15 -k 30s 20s /home/max/install.sh --unattendend --keep-zshrc || exit 0
|
sudo -u $TARGET_USER timeout -s 15 -k 30s 20s /home/$TARGET_USER/install.sh --unattendend --keep-zshrc || exit 0
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,9 @@
|
|||||||
|
|
||||||
# WARNING: UNPRIVILEGED USER (not ansible) COMMANDS
|
# WARNING: UNPRIVILEGED USER (not ansible) COMMANDS
|
||||||
- name: Install oh my zsh
|
- name: Install oh my zsh
|
||||||
remote_user: "{{ human_admin_user }}"
|
remote_user: ansible
|
||||||
ansible.builtin.command: "/home/{{ human_admin_user }}/install_omz.sh"
|
ansible.builtin.command: "/home/{{ human_admin_user }}/install_omz.sh {{ human_admin_user }}"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
vars:
|
|
||||||
|
|
||||||
- name: Configure oh my zsh, by pushing the config file
|
- name: Configure oh my zsh, by pushing the config file
|
||||||
remote_user: ansible
|
remote_user: ansible
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default become method
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_become_method: "sudo"
|
||||||
|
|
||||||
|
- name: Prompt for become method with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Choose become method, available: sudo, su, pbrun, pfexec, doas, dzdo, ksu, runas, machinectl [sudo]"
|
||||||
|
register: become_method_result
|
||||||
|
until: >
|
||||||
|
become_method_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
become_method_result.user_input in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl']
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_become_method: "{{ become_method_result.user_input }}"
|
||||||
|
when: become_method_result.user_input | length > 0
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default hostname
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_hostname: "test-guest"
|
||||||
|
|
||||||
|
- name: Prompt for hostname with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Enter hostname (letters, digits, hyphens (but not leading and trailing), dots; max 253 chars) [{{ guest_hostname }}]"
|
||||||
|
register: hostname_result
|
||||||
|
until: >
|
||||||
|
hostname_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
hostname_result.user_input | length <= 253 and
|
||||||
|
hostname_result.user_input is regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$')
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_hostname: "{{ hostname_result.user_input }}"
|
||||||
|
when: hostname_result.user_input | length > 0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default human admin user
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
create_human_admin_user: true
|
||||||
|
|
||||||
|
- name: Prompt for human admin user creation with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Create human admin user? (true/false) [true]"
|
||||||
|
register: human_admin_user_result
|
||||||
|
until: >
|
||||||
|
human_admin_user_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
human_admin_user_result.user_input in ['true', 'false']
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
create_human_admin_user: "{{ human_admin_user_result.user_input }}"
|
||||||
|
when: human_admin_user_result.user_input | length > 0
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default ip
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_ip_address: "192.168.0.90"
|
||||||
|
|
||||||
|
- name: Set default network mask
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_netmask: "24"
|
||||||
|
|
||||||
|
- name: Prompt for IP with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Enter ip in x.x.x.x/x format [192.168.0.90/24]"
|
||||||
|
register: ip_result
|
||||||
|
until: >
|
||||||
|
ip_result.user_input | length == 0 or
|
||||||
|
ip_result.user_input is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)/(3[0-2]|[12]?\\d)$')
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default (IP)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_ip_address: "{{ ip_result.user_input.split('/') | first }}"
|
||||||
|
when: ip_result.user_input | length > 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default (netmask)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_netmask: "{{ ip_result.user_input.split('/')[1] }}"
|
||||||
|
when: ip_result.user_input | length > 0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default privileged
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lxc_privileged: false
|
||||||
|
|
||||||
|
- name: Prompt for privileged with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Privileged LXC? (true/false) [false]"
|
||||||
|
register: privileged_result
|
||||||
|
until: >
|
||||||
|
privileged_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
privileged_result.user_input in ['true', 'false']
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lxc_privileged: "{{ privileged_result.user_input }}"
|
||||||
|
when: privileged_result.user_input | length > 0
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default password
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lxc_root_password: "test-guest"
|
||||||
|
|
||||||
|
- name: Prompt for password
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Enter your password [{{ lxc_root_password }}]"
|
||||||
|
echo: false
|
||||||
|
register: password_result_1
|
||||||
|
|
||||||
|
- name: Prompt for password confirmation (only if a password was entered)
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Confirm your new password"
|
||||||
|
echo: false
|
||||||
|
register: password_result_2
|
||||||
|
when: password_result_1.user_input | length > 0
|
||||||
|
|
||||||
|
- name: Ensure passwords match
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- password_result_1.user_input == password_result_2.user_input
|
||||||
|
fail_msg: "Passwords do not match!"
|
||||||
|
success_msg: "Password confirmed."
|
||||||
|
when: password_result_1.user_input | length > 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lxc_root_password: "{{ password_result_1.user_input }}"
|
||||||
|
when: password_result_1.user_input | length > 0
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default OS template
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_os_template: "debian-13"
|
||||||
|
|
||||||
|
- name: Prompt for OS template with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Choose OS template, available: debian-13, alpine [debian-13]"
|
||||||
|
register: os_template_result
|
||||||
|
until: >
|
||||||
|
os_template_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
os_template_result.user_input in ['debian-13', 'alpine']
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_os_template: "{{ os_template_result.user_input }}"
|
||||||
|
when: os_template_result.user_input | length > 0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Set default storage
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_storage: "main"
|
||||||
|
|
||||||
|
- name: Prompt for guest storage with immediate validation
|
||||||
|
ansible.builtin.pause:
|
||||||
|
prompt: "Choose storage for root, available: main (HDD), local (PCIe NVMe SSD) [{{ guest_storage }}]"
|
||||||
|
register: storage_result
|
||||||
|
until: >
|
||||||
|
storage_result.user_input | length == 0 or
|
||||||
|
(
|
||||||
|
storage_result.user_input in ['main', 'local']
|
||||||
|
)
|
||||||
|
retries: 2
|
||||||
|
delay: 0
|
||||||
|
|
||||||
|
- name: Use user input only if provided, otherwise keep the default
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
guest_storage: "{{ storage_result.user_input }}"
|
||||||
|
when: storage_result.user_input | length > 0
|
||||||
Reference in New Issue
Block a user