diff --git a/README.md b/README.md index a339fc5..ad34441 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ This repository contains Ansible playbooks and roles for bootstrapping a fresh P ```yaml # group_vars/all/vault.yml - initial_root_password: "YourActualProxmoxRootPassword" + vault_proxmox_initial_root_password: "YourActualProxmoxRootPassword" ``` Save and exit. diff --git a/group_vars/proxmox/main.yml b/group_vars/proxmox/main.yml index 7620ea4..ea8a4e5 100644 --- a/group_vars/proxmox/main.yml +++ b/group_vars/proxmox/main.yml @@ -9,17 +9,43 @@ proxmox_network_cidr: "24" proxmox_network_gateway: "192.168.1.1" proxmox_physical_nic: "eno1" # Main NIC for vmbr0 -# General system-wide variables -admin: - name: "plasmagoat" - groups: ["sudo"] - shell: /bin/bash - ssh_keys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUP7m8jZJiclZGfSje8CeBYFhX10SrdtjYziuChmj1X plasmagoat@macbook-air" +# Proxmox API +# proxmox_role_id: CI_VM_Admin +# proxmox_role_privs: "VM.Allocate,VM.Audit,VM.Clone,VM.PowerMgmt,Datastore.Allocate,Datastore.Audit,Datastore.Read,Datastore.Backup,Sys.Audit" +proxmox_api_user_name: ci-user # Name for the Proxmox API user +proxmox_api_user_realm: pam # Realm for the Proxmox API user (e.g., 'pam', 'pve') +proxmox_api_user_role: PVEAdmin +proxmox_api_user_password: "{{ vault_proxmox_api_user_password }}" # Securely retrieve password +proxmox_api_token_id: ci-token # e.g., 'ci-token' +proxmox_api_token_comment: "Token for CI/CD operations on Proxmox" +proxmox_api_token_privs: # Privileges for the API Token (often defined by assigned roles) + # For a token, you typically rely on the user's roles. But you can also explicitly grant + # or restrict privileges directly on the token. Here, we'll rely on the user's role. + # You can override here if needed, e.g., ['VM.PowerMgmt', 'VM.Clone'] + [] +# This should be retrieved from a secure source like Ansible Vault or environment variables. +# proxmox_api_token_secret: "{{ lookup('ansible.builtin.env', 'PROXMOX_API_TOKEN_SECRET') }}" +# OR, if using Ansible Vault: +proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}" -ci_user: - name: forgejo-runner - groups: ["sudo"] - shell: /bin/bash - ssh_keys: - - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGlzZWik5bbH6/xjiCpwo1SQSJ/J/Cv7y4ZQ45P68GLB forgejo-runner" +# --- Proxmox Connection Details --- +proxmox_host: "192.168.1.205" # Proxmox API IP/hostname +proxmox_initial_root_password: "{{ vault_proxmox_initial_root_password }}" + +proxmox_admin_user_name: "plasmagoat" +proxmox_admin_user_groups: ["sudo"] +proxmox_admin_user_shell: /bin/bash +proxmox_admin_user_ssh_keys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUP7m8jZJiclZGfSje8CeBYFhX10SrdtjYziuChmj1X plasmagoat@macbook-air" + +proxmox_ci_user_name: ci-user +proxmox_ci_user_groups: ["sudo"] +proxmox_ci_user_shell: /bin/bash +proxmox_ci_user_ssh_key: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGlzZWik5bbH6/xjiCpwo1SQSJ/J/Cv7y4ZQ45P68GLB forgejo-runner" +# ci_user: +# name: forgejo-runner +# groups: ["sudo"] +# shell: /bin/bash +# ssh_keys: +# - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGlzZWik5bbH6/xjiCpwo1SQSJ/J/Cv7y4ZQ45P68GLB forgejo-runner" diff --git a/group_vars/proxmox/vault.yml b/group_vars/proxmox/vault.yml index 0b0fb6e..9548f41 100644 --- a/group_vars/proxmox/vault.yml +++ b/group_vars/proxmox/vault.yml @@ -1,7 +1,14 @@ $ANSIBLE_VAULT;1.1;AES256 -61313964636437313765633263626265306663373866616265393463383838616130373639373037 -6261666639613636363666626635353636343439663263320a303137653761646664633463376466 -62616630306332373862653838376563623465393130386536383666616133656538306336666165 -3430373162633736610a633864623662366536353436343235353764386664376662363138376435 -66633337393735633539303565663634333635366462386465313739613762613932643231656437 -3464393961663935373964623432383834643263353230313333 +35353166376230643231373732353138333738383563366536383031656335303630623238626663 +3934363333653763353839363435393964626437616138360a643238643032613332396135313766 +38323266326262643637366364643663336166353365613139383166356233336137613961316233 +6130613761356635340a353839353434623861383363643663643930306431336134336437623663 +34383235653461306631396439376462313062343031313632386339386434663365613732376431 +62653461356465336633613366383533356139316662653862336438356136643964653733333230 +33373266656261306630636131393635656562343466303836366262646634303335613861326530 +34643439653463646431373063633830323238393565306436623832633930326533626139336234 +66616636333130616366366339393631316265363565623532303132373162666561396562336363 +39643838653364366539386466356565366335653261396563306133323965363837326164393336 +64313364393439643163633330303862373135376266643863633764343462336164303562386561 +61363638313432363366636662333763313163613862326133633330383463633831613265623466 +36303965353832313433383865656432633137376439336365346632313438633161 diff --git a/playbooks/bootstrap.yml b/playbooks/bootstrap.yml index b2c4504..366bf82 100644 --- a/playbooks/bootstrap.yml +++ b/playbooks/bootstrap.yml @@ -4,7 +4,7 @@ become: true # Use sudo for all tasks vars: - ansible_become_pass: "{{ initial_root_password }}" # Use the vaulted root password for initial connection + ansible_become_pass: "{{ vault_proxmox_initial_root_password }}" # Use the vaulted root password for initial connection pre_tasks: - name: Wait for SSH to be available @@ -20,5 +20,6 @@ - role: common - role: proxmox_setup - role: ci_user + - role: proxmox_api # - role: cloudinit # - role: networking # Uncomment if you've populated this role diff --git a/roles/ci_user/tasks/main.yml b/roles/ci_user/tasks/main.yml index 4bd2253..4d0023a 100644 --- a/roles/ci_user/tasks/main.yml +++ b/roles/ci_user/tasks/main.yml @@ -1,20 +1,34 @@ +- name: Create CI group + ansible.builtin.group: + name: ci + state: present + - name: Create CI user ansible.builtin.user: - name: "{{ ci_user.name }}" - groups: "{{ ci_user.groups }}" - shell: "{{ ci_user.shell }}" + name: "{{ proxmox_ci_user_name }}" + group: ci + groups: "{{ proxmox_ci_user_groups }}" + shell: "{{ proxmox_ci_user_shell }}" state: present create_home: yes - when: ci_user.name is defined and ci_user.name | length > 0 + when: proxmox_ci_user_name is defined and proxmox_ci_user_name | length > 0 - name: Add SSH keys for CI user ansible.posix.authorized_key: - user: "{{ ci_user.name }}" + user: "{{ proxmox_ci_user_name }}" state: present key: "{{ item }}" - loop: "{{ ci_user.ssh_keys }}" + loop: "{{ proxmox_ci_user_ssh_keys }}" when: - - ci_user.name is defined - - ci_user.name | length > 0 - - ci_user.ssh_keys is defined - - ci_user.ssh_keys | length > 0 + - proxmox_ci_user_name is defined + - proxmox_ci_user_name | length > 0 + - proxmox_ci_user_ssh_keys is defined + - proxmox_ci_user_ssh_keys | length > 0 + +- name: Ensure image directory exists with correct permissions + ansible.builtin.file: + path: /var/lib/vz/dump + state: directory + owner: root + group: ci + mode: "0775" # rwxrwxr-x so 'ci' can write, others can read/execute diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index a3abf1c..52c96ff 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -15,30 +15,31 @@ - htop - git - rsync + - jq - nfs-common # If you plan to mount NFS shares state: present - name: Create new admin user ansible.builtin.user: - name: "{{ admin.name }}" - groups: "{{ admin.groups }}" - shell: "{{ admin.shell }}" + name: "{{ proxmox_admin_user_name }}" + groups: "{{ proxmox_admin_user_groups }}" + shell: "{{ proxmox_admin_user_shell }}" state: present create_home: yes append: yes # Ensures other groups don't get removed - when: admin.name is defined and admin.name | length > 0 + when: proxmox_admin_user_name is defined and proxmox_admin_user_name | length > 0 - name: Add SSH keys for new admin user ansible.posix.authorized_key: - user: "{{ admin.name }}" + user: "{{ proxmox_admin_user_name }}" state: present key: "{{ item }}" - loop: "{{ admin.ssh_keys }}" + loop: "{{ proxmox_admin_user_ssh_keys }}" when: - - admin.name is defined - - admin.name | length > 0 - - admin.ssh_keys is defined - - admin.ssh_keys | length > 0 + - proxmox_admin_user_name is defined + - proxmox_admin_user_name | length > 0 + - proxmox_admin_user_ssh_keys is defined + - proxmox_admin_user_ssh_keys | length > 0 # - name: Disable root SSH login (optional, but recommended) # ansible.builtin.lineinfile: # path: /etc/ssh/sshd_config diff --git a/roles/proxmox_api/tasks/main.yml b/roles/proxmox_api/tasks/main.yml new file mode 100644 index 0000000..54a61a7 --- /dev/null +++ b/roles/proxmox_api/tasks/main.yml @@ -0,0 +1,57 @@ +- name: Ensure Proxmox API user exists and has correct password (using pveum) + ansible.builtin.shell: | + set -e # Exit immediately if a command exits with a non-zero status + USER_EXISTS=$(pveum user list --output-format json | jq -r '.[] | select(.userid == "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}") | .userid') + + if [ -z "$USER_EXISTS" ]; then + pveum user add "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" --password "{{ proxmox_api_user_password }}" --comment "CI/CD user created by Ansible" -enable 1 + echo "User '{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}' was added." + else + # Always attempt to modify to ensure password/comment is up-to-date + # pveum user modify does not return 'modified' for idempotency + (echo "{{ proxmox_api_user_password }}"; echo "{{ proxmox_api_user_password }}") | pveum passwd "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" + pveum user modify "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" --comment "CI/CD user updated by Ansible" + echo "User '{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}' was modified (or already up-to-date)." + fi + register: pveum_user_result + changed_when: "'added' in pveum_user_result.stdout or 'modified' in pveum_user_result.stdout" + failed_when: pveum_user_result.rc != 0 and 'already exists' not in pveum_user_result.stderr + no_log: true # Prevent password from being logged + +- name: Ensure Proxmox API token exists (using pveum) + ansible.builtin.shell: | + set -e + TOKEN_INFO=$(pveum user token list "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" --output-format json 2>/dev/null | jq -r '.[] | select(.tokenid == "{{ proxmox_api_token_id }}")') + + if [ -z "$TOKEN_INFO" ]; then + pveum user token add "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" "{{ proxmox_api_token_id }}" --comment "CI/CD token created by Ansible" + # echo "Token '{{ proxmox_api_token_id }}' was added. Secret: $(cat ~/.pve/token-{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}!{{ proxmox_api_token_id }}.json | jq -r '.value')" # Capture secret + else + echo "Token '{{ proxmox_api_token_id }}' for user '{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}' already exists. No action taken." + fi + register: pveum_token_result + changed_when: "'was added' in pveum_token_result.stdout" + # This task is tricky for secret capture. + # `pveum user token add` prints the secret to stdout on creation AND saves it to a file. + # We try to capture it from stdout and then from the file for robustness. + # You MUST parse `pveum_token_result.stdout` to get the secret when it's new. + # In real CI/CD, generate a new token via pveum only ONCE and store the secret + # then use `proxmox_api_token_secret` in your vars. + no_log: false # Prevent password and token secret from being logged + +- debug: + var: pveum_token_result.stdout + +- name: Ensure ACL for root exists + ansible.builtin.shell: | + set -e + ACL_EXISTS=$(pveum acl list --output-format json 2>/dev/null | jq -r '.[] | select(.path == "/" and .user == "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" and .roleid == "{{ proxmox_api_user_role }}") | .path') + + if [ -z "$ACL_EXISTS" ]; then + pveum acl modify / -user "{{ proxmox_api_user_name }}@{{ proxmox_api_user_realm }}" -role "{{ proxmox_api_user_role }}" -propagate 1 + echo "ACL for / was added." + else + echo "ACL for / already exists." + fi + register: pveum_acl_root_result + changed_when: "'was added' in pveum_acl_root_result.stdout" diff --git a/roles/user/tasks/main.yml b/roles/user/tasks/main.yml deleted file mode 100644 index 09d1fc7..0000000 --- a/roles/user/tasks/main.yml +++ /dev/null @@ -1,11 +0,0 @@ -- name: Create CI user - ansible.builtin.user: - name: "{{ ci_user }}" - shell: /bin/bash - groups: sudo - password: "{{ ci_password | password_hash('sha512') }}" - -- name: Add authorized key - ansible.posix.authorized_key: - user: "{{ ci_user }}" - key: "{{ lookup('file', '../files/ci_user.pub') }}"