diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index 86440ca..40d8c51 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -10,76 +10,30 @@ ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" - vars_prompt: - - name: lxc_hostname - prompt: "Hostname for the container (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)" - default: "new-lxc-test" - private: false + pre_tasks: + - name: Include input and validation for hostname + ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml + + - name: Include input and validation for LXC root password + ansible.builtin.include_tasks: ../tasks/lxc_root_password_validation.yaml - - name: lxc_root_password - prompt: "Root password" - default: "new-lxc-test" - private: true - confirm: true - encrypt: sha512_crypt + - name: Include input and validation for IP + ansible.builtin.include_tasks: ../tasks/ip_validation.yaml - - name: lxc_ip_address - prompt: "IP address for the container (in x.x.x.x/x formant)" - default: "192.168.0.90/24" - private: false + - name: Include input and validation for LXC privilege + ansible.builtin.include_tasks: ../tasks/lxc_privileged_validation.yaml - - name: lxc_is_privileged - prompt: "Unprivileged LXC (true/false)?" - default: true - private: false + - name: Include input and validation for human admin user creation + ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml - - name: lxc_create_human_admin_user - prompt: "Create human admin user? (true/false)?" - default: true - private: false - - pre_tasks: - - name: Validate hostname - ansible.builtin.fail: - msg: | - Invalid hostname: "{{ lxc_hostname }}" - Hostname must: - - contain only letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) - - not start or end with a hyphen or dot - - have each label between 1 and 63 characters - - not exceed 253 characters in total - when: > - lxc_hostname | length > 253 or - lxc_hostname is not 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])?)*$') - 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/deploy_lxc_on_proxmox 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 ansible.builtin.add_host: - name: "{{ fact_lxc_ip_address }}" + name: "{{ guest_ip_address }}" groups: new_host ansible_user: ansible ansible_become_password: "{{ ansible_become_passwd }}" @@ -104,10 +58,10 @@ - name: Include human admin user role ansible.builtin.include_role: 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 ansible.builtin.include_role: name: ../roles/update_configs - when: "hostvars['localhost']['fact_lxc_create_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" diff --git a/playbooks/deploy_vm_from_cloud_init.yaml b/playbooks/deploy_vm_from_cloud_init.yaml index 3053194..9f28a50 100644 --- a/playbooks/deploy_vm_from_cloud_init.yaml +++ b/playbooks/deploy_vm_from_cloud_init.yaml @@ -10,98 +10,33 @@ ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" 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: - - name: Validate hostname - ansible.builtin.fail: - msg: | - Invalid hostname: "{{ lxc_hostname }}" - Hostname must: - - contain only letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) - - not start or end with a hyphen or dot - - have each label between 1 and 63 characters - - not exceed 253 characters in total - when: > - lxc_hostname | length > 253 or - lxc_hostname is not 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])?)*$') - 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: Include input and validation for hostname + ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml - - name: Validate become method - ansible.builtin.fail: - msg: | - Invalid become method: "{{ vm_become_method }}" - Select from available options: sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl - when: vm_become_method not in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl'] - run_once: true + - name: Include input and validation for IP + ansible.builtin.include_tasks: ../tasks/ip_validation.yaml + + - name: Include input and validation for human admin user creation + ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml + + - 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 roles: - ../roles/deploy_vm_on_proxmox 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 ansible.builtin.add_host: - name: "{{ fact_vm_ip_address }}" + name: "{{ guest_ip_address }}" groups: new_host ansible_user: ansible ansible_become_password: "{{ ansible_become_passwd }}" - ansible_become_method: "{{ vm_become_method }}" + ansible_become_method: "{{ guest_become_method }}" - name: Configure VM @@ -123,10 +58,10 @@ - name: Include human admin user role ansible.builtin.include_role: 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 ansible.builtin.include_role: name: ../roles/update_configs - when: "hostvars['localhost']['fact_vm_create_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" diff --git a/roles/deploy_lxc_on_proxmox/tasks/main.yaml b/roles/deploy_lxc_on_proxmox/tasks/main.yaml index b396bd4..d3e6261 100644 --- a/roles/deploy_lxc_on_proxmox/tasks/main.yaml +++ b/roles/deploy_lxc_on_proxmox/tasks/main.yaml @@ -12,9 +12,9 @@ cmode: "shell" password: "{{ lxc_root_password }}" - hostname: "{{ lxc_hostname }}" + hostname: "{{ guest_hostname }}" ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz' - unprivileged: "{{ lxc_is_privileged }}" + unprivileged: "{{ lxc_privileged }}" memory: 2048 cores: 2 state: present @@ -26,7 +26,7 @@ storage: local pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" 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 become: false @@ -38,7 +38,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - hostname: "{{ lxc_hostname }}" + hostname: "{{ guest_hostname }}" state: started retries: 3 delay: 15 diff --git a/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml b/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml index 4de80a0..effc35c 100644 --- a/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml @@ -10,8 +10,8 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - clone: "{{ vm_os_template }}-cloud-init-template" - name: "{{ vm_hostname }}" + clone: "{{ guest_os_template }}-cloud-init-template" + name: "{{ guest_hostname }}" storage: main format: qcow2 full: true @@ -33,7 +33,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" # Cloud-init parameters update: true ide: @@ -41,7 +41,7 @@ ciuser: "ansible" cipassword: "{{ ansible_become_passwd }}" 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 become: false @@ -53,7 +53,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" cores: 4 memory: 4096 # Minimal value for debian based OS for smooth workflow update: true @@ -68,5 +68,5 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: started diff --git a/roles/deploy_vm_on_proxmox/tasks/main.yaml b/roles/deploy_vm_on_proxmox/tasks/main.yaml index d54ec6d..7c17320 100644 --- a/roles/deploy_vm_on_proxmox/tasks/main.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/main.yaml @@ -9,7 +9,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: current ignore_errors: true register: vm_status diff --git a/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml b/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml index 8140a72..4bc6f4e 100644 --- a/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml @@ -10,7 +10,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: started - name: Sleep for 120 seconds to ensure that Proxmox has cloned the VM diff --git a/tasks/become_method_validation.yaml b/tasks/become_method_validation.yaml new file mode 100644 index 0000000..412a7c1 --- /dev/null +++ b/tasks/become_method_validation.yaml @@ -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 diff --git a/tasks/hostname_validation.yaml b/tasks/hostname_validation.yaml new file mode 100644 index 0000000..9dd9f81 --- /dev/null +++ b/tasks/hostname_validation.yaml @@ -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 diff --git a/tasks/human_admin_user_validation.yaml b/tasks/human_admin_user_validation.yaml new file mode 100644 index 0000000..849ba7b --- /dev/null +++ b/tasks/human_admin_user_validation.yaml @@ -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 diff --git a/tasks/ip_validation.yaml b/tasks/ip_validation.yaml new file mode 100644 index 0000000..eabcd78 --- /dev/null +++ b/tasks/ip_validation.yaml @@ -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('/') | second }}" + when: ip_result.user_input | length > 0 diff --git a/tasks/lxc_privileged_validation.yaml b/tasks/lxc_privileged_validation.yaml new file mode 100644 index 0000000..5b4c3af --- /dev/null +++ b/tasks/lxc_privileged_validation.yaml @@ -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 diff --git a/tasks/lxc_root_password_validation.yaml b/tasks/lxc_root_password_validation.yaml new file mode 100644 index 0000000..845a49f --- /dev/null +++ b/tasks/lxc_root_password_validation.yaml @@ -0,0 +1,33 @@ +--- + +- 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 + until: password_result_1.user_input | length == 0 + retries: 2 + delay: 0 + +- name: Prompt for password (confirmation) + ansible.builtin.pause: + prompt: "Confirm your new password" + echo: false + register: password_result_2 + +- 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." + +- 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 + diff --git a/tasks/os_template_validation.yaml b/tasks/os_template_validation.yaml new file mode 100644 index 0000000..27f819b --- /dev/null +++ b/tasks/os_template_validation.yaml @@ -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