diff --git a/ansible/ANSIBLE.md b/ansible/ANSIBLE.md new file mode 100644 index 0000000..d363b32 --- /dev/null +++ b/ansible/ANSIBLE.md @@ -0,0 +1 @@ +ansible-galaxy collection install -r requirements.yml diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..4aabaed --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory = ./inventory/hosts.yml +remote_user = root +host_key_checking = false +roles_path = ./roles diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..10005ea --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,2 @@ +[proxmox] +proxmox-01 ansible_host=192.168.1.205 ansible_user=plasmagoat diff --git a/ansible/inventory/group_vars/all.yml b/ansible/inventory/group_vars/all.yml new file mode 100644 index 0000000..8b174b3 --- /dev/null +++ b/ansible/inventory/group_vars/all.yml @@ -0,0 +1,14 @@ +# VM/Template Configuration +backup_template_vmid: 9101 +backup_template_vm_name: nixos-base-backup +latest_template_vmid: 9100 +latest_template_vm_name: nixos-base-latest + +storage_name: local-lvm # Proxmox storage to use (e.g., local-lvm, local) + +result_path: "{{ playbook_dir }}/../result" # Build output directory +dest_image_path: "/var/lib/vz/dump/" # Directory on Proxmox to upload images + +# Configuration for the restored VM +cpu_cores: 2 +memory_mb: 2048 diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml new file mode 100644 index 0000000..926988f --- /dev/null +++ b/ansible/inventory/hosts.yml @@ -0,0 +1,7 @@ +--- +all: + children: + proxmox: + hosts: + proxmox-01: + ansible_host: 192.168.1.205 # Replace with your Proxmox host IP/hostname diff --git a/ansible/playbooks/create-template.yml b/ansible/playbooks/create-template.yml new file mode 100644 index 0000000..c84227c --- /dev/null +++ b/ansible/playbooks/create-template.yml @@ -0,0 +1,16 @@ +- name: Restore and Convert to Template on Proxmox + hosts: proxmox # Target the Proxmox host + become: true # Need root/sudo on Proxmox host for qm commands + + vars: + # VM/Template specifics (can be passed via --extra-vars or from group_vars) + vmid_base_template: "{{ template_vmid }}" + vmname_base_template: "{{ template_vm_name }}" + vmid_latest_template: "{{ latest_template_vmid }}" + vmname_latest_template: "{{ latest_template_vm_name }}" + + # Configuration for the restored VM + cpu_cores: 2 + memory_mb: 2048 + + tasks: diff --git a/ansible/playbooks/image-deploy.yml b/ansible/playbooks/image-deploy.yml new file mode 100644 index 0000000..1dbc2b9 --- /dev/null +++ b/ansible/playbooks/image-deploy.yml @@ -0,0 +1,130 @@ +--- +- name: Build and Upload NixOS Image + hosts: localhost # Run NixOS build and image upload on the CI runner or local machine + gather_facts: false # No need to gather facts for localhost + + vars: + nixos_build_path: "{{ playbook_dir }}/../" # Path to your flake.nix + result_symlink_path: "{{ nixos_build_path }}/result" # Expected symlink output from Nix + dest_dir: "{{ dest_image_dir }}" # From group_vars/all.yml + + tasks: + - name: Ensure NixOS image build script is executable + ansible.builtin.file: + path: "{{ playbook_dir }}/../scripts/build_nixos_image.sh" + mode: "0755" + + - name: Build NixOS image (creates result/ symlink) + ansible.builtin.command: "{{ playbook_dir }}/../scripts/build_nixos_image.sh" + args: + chdir: "{{ nixos_build_path }}" + register: nixos_build_result + changed_when: nixos_build_result.rc == 0 # Consider it changed if build succeeds + + - name: Get built image file (.vma.zst) from result/ + ansible.builtin.find: + paths: "{{ result_symlink_path }}" + patterns: "*.vma.zst" + file_type: file # Ensure it's a file + register: built_image_files + delegate_to: localhost + + - name: Fail if no image was built + ansible.builtin.fail: + msg: "No .vma.zst image file found in {{ result_symlink_path }}/" + when: built_image_files.files | length == 0 + delegate_to: localhost + + - name: Set fact for built image path and filename + ansible.builtin.set_fact: + local_image_path: "{{ built_image_files.files[0].path | realpath }}" + image_filename: "{{ built_image_files.files[0].path | basename }}" + delegate_to: localhost + + - name: Display paths (for debugging) + ansible.builtin.debug: + msg: "Local image path: {{ local_image_path }}, Filename: {{ image_filename }}" + + - name: Copy image to Proxmox server + ansible.builtin.copy: + src: "{{ local_image_path }}" + dest: "{{ dest_dir }}{{ image_filename }}" + mode: "0644" # Ensure correct permissions on the destination + + - name: Clean up local build result symlink + ansible.builtin.file: + path: "{{ result_symlink_path }}" + state: absent + delegate_to: localhost + + - name: Clean up local store paths (optional, for disk space on CI) + ansible.builtin.command: "nix store gc" + args: + chdir: "{{ nixos_build_path }}" + changed_when: true # Always consider it a change + +- name: Restore and Convert to Template on Proxmox + hosts: proxmox_servers # Target the Proxmox host + become: true # Need root/sudo on Proxmox host for qm commands + + vars: + # Use variables from group_vars/all.yml and vault.yml + # Access API credentials from vault for this part + proxmox_api_user_id: "{{ proxmox_ci_api_user_name }}@{{ proxmox_ci_api_user_realm }}" + proxmox_api_token_id: "{{ proxmox_ci_api_token_id }}" + proxmox_api_token_secret: "{{ proxmox_ci_api_token_secret }}" + + # VM/Template specifics (can be passed via --extra-vars or from group_vars) + vmid_base_template: "{{ template_vmid }}" + vmname_base_template: "{{ template_vm_name }}" + vmid_latest_template: "{{ latest_template_vmid }}" + vmname_latest_template: "{{ latest_template_vm_name }}" + + # Configuration for the restored VM + cpu_cores: 2 + memory_mb: 2048 + + tasks: + - name: Set full image path on Proxmox + ansible.builtin.set_fact: + remote_image_path: "{{ dest_image_dir }}{{ image_filename }}" + delegate_to: localhost # Run this locally to set a fact accessible globally + + - name: Destroy existing base template VM (if it exists) + ansible.builtin.shell: "qm destroy {{ vmid_base_template }} --purge || true" + args: + warn: no + changed_when: false # Assume idempotency; if it didn't exist, no change + failed_when: false # Don't fail if VM isn't found + + - name: Destroy existing 'latest' template VM (if it exists) + ansible.builtin.shell: "qm destroy {{ vmid_latest_template }} --purge || true" + args: + warn: no + changed_when: false + failed_when: false + + - name: Restore VM from image to base template VMID + ansible.builtin.command: > + qmrestore {{ remote_image_path }} {{ vmid_base_template }} --unique true --name {{ vmname_base_template }} --storage {{ storage_name }} + args: + creates: "/etc/pve/qemu-server/{{ vmid_base_template }}.conf" # Idempotency check: only run if config file doesn't exist + + - name: Set CPU and memory for the base template VM + ansible.builtin.command: > + qm set {{ vmid_base_template }} --cores {{ cpu_cores }} --memory {{ memory_mb }} + # This task is not fully idempotent; it will always try to set. + # You'd need complex 'when' conditions to check current settings. + + - name: Convert base template VM to a template + ansible.builtin.command: "qm template {{ vmid_base_template }}" + # This command is largely idempotent for the `qm template` operation itself. + + - name: Clone base template to 'latest' template VMID + ansible.builtin.command: > + qm clone {{ vmid_base_template }} {{ vmid_latest_template }} --name {{ vmname_latest_template }} --full --storage {{ storage_name }} + args: + creates: "/etc/pve/qemu-server/{{ vmid_latest_template }}.conf" # Idempotency check: only run if config file doesn't exist + + - name: Convert 'latest' template VM to a template + ansible.builtin.command: "qm template {{ vmid_latest_template }}" diff --git a/ansible/playbooks/upload-image.yml b/ansible/playbooks/upload-image.yml new file mode 100644 index 0000000..300c785 --- /dev/null +++ b/ansible/playbooks/upload-image.yml @@ -0,0 +1,38 @@ +- name: Build and Upload NixOS Image, Restore and Convert to Template + hosts: proxmox + gather_facts: false + + vars: + image_dir: "{{ playbook_dir }}/../result" + dest_dir: "/var/lib/vz/dump/" + + tasks: + - name: Get built image file (.vma.zst) from result/ + ansible.builtin.find: + paths: "{{ result_path }}" + patterns: "*.vma.zst" + file_type: file # Ensure it's a file + register: built_image_files + delegate_to: localhost + + - name: Fail if no image was built + ansible.builtin.fail: + msg: "No .vma.zst image file found in {{ result_path }}/" + when: built_image_files.files | length == 0 + delegate_to: localhost + + - name: Set fact for built image path and filename + ansible.builtin.set_fact: + local_image_path: "{{ built_image_files.files[0].path | realpath }}" + image_filename: "{{ built_image_files.files[0].path | basename }}" + delegate_to: localhost + + - name: Display paths (for debugging) + ansible.builtin.debug: + msg: "Local image path: {{ local_image_path }}, Filename: {{ image_filename }}" + + - name: Copy image to Proxmox server + ansible.builtin.copy: + src: "{{ local_image_path }}" + dest: "{{ dest_dir }}" + mode: "0644" # Ensure correct permissions on the destination diff --git a/ansible/roles/create-template/tasks/main.yml b/ansible/roles/create-template/tasks/main.yml new file mode 100644 index 0000000..3247fa1 --- /dev/null +++ b/ansible/roles/create-template/tasks/main.yml @@ -0,0 +1,37 @@ +- name: Set full image path on Proxmox + ansible.builtin.set_fact: + remote_image_path: "{{ dest_image_path }}{{ image_filename }}" + delegate_to: localhost # Run this locally to set a fact accessible globally + +- name: Destroy existing backup template VM (if it exists) + ansible.builtin.shell: "qm destroy {{ vmid_backup_template }} --purge || true" + # args: + # warn: no + # changed_when: false # Assume idempotency; if it didn't exist, no change + failed_when: false # Don't fail if VM isn't found + +- name: Clone 'lastest' template to 'backup' template VMID + ansible.builtin.command: > + qm clone {{ vmid_latest_template }} {{ vmid_backup_template }} --name {{ vmname_backup_template }} --full + # args: + # creates: "/etc/pve/qemu-server/{{ vmid_backup_template }}.conf" # Idempotency check: only run if config file doesn't exist + # failed_when: false # Don't fail if VM isn't found + +- name: Convert 'backup' template VM to a template + ansible.builtin.command: "qm template {{ vmid_backup_template }}" + +- name: Destroy existing backup template VM (if it exists) + ansible.builtin.shell: "qm destroy {{ vmid_backup_template }} --purge || true" + +- name: Restore VM from image to 'latest' template VMID + ansible.builtin.command: > + qmrestore {{ remote_image_path }} {{ vmid_latest_template }} --unique true + # args: + # creates: "/etc/pve/qemu-server/{{ vmid_latest_template }}.conf" # Idempotency check: only run if config file doesn't exist + +- name: Set CPU and memory for the base template VM + ansible.builtin.command: > + qm set {{ vmid_latest_template }} --cores {{ cpu_cores }} --memory {{ memory_mb }} --name {{ vmname_latest_template }} + +- name: Convert 'backup' template VM to a template + ansible.builtin.command: "qm template {{ vmid_latest_template }}" diff --git a/ansible/roles/upload/tasks/main.yml b/ansible/roles/upload/tasks/main.yml new file mode 100644 index 0000000..4b5ac6b --- /dev/null +++ b/ansible/roles/upload/tasks/main.yml @@ -0,0 +1,29 @@ +- name: Get built image file (.vma.zst) from result/ + ansible.builtin.find: + paths: "{{ result_path }}" + patterns: "*.vma.zst" + file_type: file # Ensure it's a file + register: built_image_files + delegate_to: localhost + +- name: Fail if no image was built + ansible.builtin.fail: + msg: "No .vma.zst image file found in {{ result_path }}/" + when: built_image_files.files | length == 0 + delegate_to: localhost + +- name: Set fact for built image path and filename + ansible.builtin.set_fact: + local_image_path: "{{ built_image_files.files[0].path | realpath }}" + image_filename: "{{ built_image_files.files[0].path | basename }}" + delegate_to: localhost + +- name: Display paths (for debugging) + ansible.builtin.debug: + msg: "Local image path: {{ local_image_path }}, Filename: {{ image_filename }}" + +- name: Copy image to Proxmox server + ansible.builtin.copy: + src: "{{ local_image_path }}" + dest: "{{ dest_image_path }}" + mode: "0644" # Ensure correct permissions on the destination diff --git a/ansible/upload-template.yml b/ansible/upload-template.yml new file mode 100644 index 0000000..82b0a7c --- /dev/null +++ b/ansible/upload-template.yml @@ -0,0 +1,20 @@ +- name: Build and Upload NixOS Image, Restore and Convert to Template + hosts: proxmox + gather_facts: false + + roles: + - role: upload + +- name: Restore and Convert to Template on Proxmox + hosts: proxmox + become: true # Need root/sudo on Proxmox host for qm commands + + vars: + # VM/Template specifics (can be passed via --extra-vars or from group_vars) + vmid_backup_template: "{{ backup_template_vmid }}" + vmname_backup_template: "{{ backup_template_vm_name }}" + vmid_latest_template: "{{ latest_template_vmid }}" + vmname_latest_template: "{{ latest_template_vm_name }}" + + roles: + - role: create-template diff --git a/ansible/vault.yml b/ansible/vault.yml new file mode 100644 index 0000000..8c31bfb --- /dev/null +++ b/ansible/vault.yml @@ -0,0 +1,15 @@ +# To edit this file: ansible-vault edit ansible/vault.yml + +# For initial Proxmox user creation (using root@pam to create the API user) +proxmox_root_password: "YOUR_PROXMOX_ROOT_PASSWORD_HERE" # CHANGE THIS AND ENCRYPT + +# For the dedicated Proxmox API user and token (for CI/CD) +proxmox_ci_api_user_name: cicd-api-user +proxmox_ci_api_user_realm: pam +proxmox_ci_api_user_password: "SUPER_STRONG_PASSWORD_FOR_API_USER" # CHANGE THIS AND ENCRYPT + +proxmox_ci_api_token_id: cicd-deploy-token +# The 'value' of the API token secret should be generated by 'pveum' or 'proxmox_api_token' module. +# You will get this after running proxmox_setup.yml for the first time. +# Then update this vault.yml with the actual secret. +proxmox_ci_api_token_secret: "pveum_token_secret_from_first_run_here" # POPULATE THIS AFTER FIRST RUN AND ENCRYPT